mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2025-06-02 19:04:33 -04:00
Merge branch 'paytrace' into v5-develop
This commit is contained in:
commit
7b73104a45
@ -742,6 +742,27 @@ class CreateSingleAccount extends Command
|
|||||||
$cg->fees_and_limits = $fees_and_limits;
|
$cg->fees_and_limits = $fees_and_limits;
|
||||||
$cg->save();
|
$cg->save();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (config('ninja.testvars.paytrace.decrypted') && ($this->gateway == 'all' || $this->gateway == 'paytrace')) {
|
||||||
|
$cg = new CompanyGateway;
|
||||||
|
$cg->company_id = $company->id;
|
||||||
|
$cg->user_id = $user->id;
|
||||||
|
$cg->gateway_key = 'bbd736b3254b0aabed6ad7fda1298c88';
|
||||||
|
$cg->require_cvv = true;
|
||||||
|
$cg->require_billing_address = true;
|
||||||
|
$cg->require_shipping_address = true;
|
||||||
|
$cg->update_details = true;
|
||||||
|
$cg->config = encrypt(config('ninja.testvars.paytrace.decrypted'));
|
||||||
|
$cg->save();
|
||||||
|
|
||||||
|
$gateway_types = $cg->driver(new Client)->gatewayTypes();
|
||||||
|
|
||||||
|
$fees_and_limits = new stdClass;
|
||||||
|
$fees_and_limits->{$gateway_types[0]} = new FeesAndLimits;
|
||||||
|
|
||||||
|
$cg->fees_and_limits = $fees_and_limits;
|
||||||
|
$cg->save();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private function createRecurringInvoice($client)
|
private function createRecurringInvoice($client)
|
||||||
|
@ -60,11 +60,12 @@ class SupportMessageSent extends Mailable
|
|||||||
|
|
||||||
$company = auth()->user()->company();
|
$company = auth()->user()->company();
|
||||||
$user = auth()->user();
|
$user = auth()->user();
|
||||||
|
$db = str_replace("db-ninja-", "", $company->db);
|
||||||
|
|
||||||
if(Ninja::isHosted())
|
if(Ninja::isHosted())
|
||||||
$subject = "{$priority}Hosted-{$company->db} :: Customer Support - {$plan} ".date('M jS, g:ia');
|
$subject = "{$priority}Hosted-{$company->db} :: {$plan} :: ".date('M jS, g:ia');
|
||||||
else
|
else
|
||||||
$subject = "{$priority}Self Hosted :: Customer Support - [{$plan}] ".date('M jS, g:ia');
|
$subject = "{$priority}Self Hosted :: {$plan} :: ".date('M jS, g:ia');
|
||||||
|
|
||||||
return $this->from(config('mail.from.address'), $user->present()->name())
|
return $this->from(config('mail.from.address'), $user->present()->name())
|
||||||
->replyTo($user->email, $user->present()->name())
|
->replyTo($user->email, $user->present()->name())
|
||||||
|
@ -69,16 +69,20 @@ class CompanyGateway extends BaseModel
|
|||||||
// const TYPE_CUSTOM = 306;
|
// const TYPE_CUSTOM = 306;
|
||||||
// const TYPE_BRAINTREE = 307;
|
// const TYPE_BRAINTREE = 307;
|
||||||
// const TYPE_WEPAY = 309;
|
// const TYPE_WEPAY = 309;
|
||||||
|
// const TYPE_PAYFAST = 310;
|
||||||
|
// const TYPE_PAYTRACE = 311;
|
||||||
|
|
||||||
public $gateway_consts = [
|
public $gateway_consts = [
|
||||||
'38f2c48af60c7dd69e04248cbb24c36e' => 300,
|
'38f2c48af60c7dd69e04248cbb24c36e' => 300,
|
||||||
'd14dd26a37cecc30fdd65700bfb55b23' => 301,
|
'd14dd26a37cecc30fdd65700bfb55b23' => 301,
|
||||||
|
'd14dd26a47cecc30fdd65700bfb67b34' => 301,
|
||||||
'3758e7f7c6f4cecf0f4f348b9a00f456' => 304,
|
'3758e7f7c6f4cecf0f4f348b9a00f456' => 304,
|
||||||
'3b6621f970ab18887c4f6dca78d3f8bb' => 305,
|
'3b6621f970ab18887c4f6dca78d3f8bb' => 305,
|
||||||
'54faab2ab6e3223dbe848b1686490baa' => 306,
|
'54faab2ab6e3223dbe848b1686490baa' => 306,
|
||||||
'd14dd26a47cecc30fdd65700bfb67b34' => 301,
|
|
||||||
'8fdeed552015b3c7b44ed6c8ebd9e992' => 309,
|
|
||||||
'f7ec488676d310683fb51802d076d713' => 307,
|
'f7ec488676d310683fb51802d076d713' => 307,
|
||||||
|
'8fdeed552015b3c7b44ed6c8ebd9e992' => 309,
|
||||||
|
'd6814fc83f45d2935e7777071e629ef9' => 310,
|
||||||
|
'bbd736b3254b0aabed6ad7fda1298c88' => 311,
|
||||||
];
|
];
|
||||||
|
|
||||||
protected $touches = [];
|
protected $touches = [];
|
||||||
|
@ -81,6 +81,9 @@ class Gateway extends StaticModel
|
|||||||
case 1:
|
case 1:
|
||||||
return [GatewayType::CREDIT_CARD => ['refund' => true, 'token_billing' => true]];//Authorize.net
|
return [GatewayType::CREDIT_CARD => ['refund' => true, 'token_billing' => true]];//Authorize.net
|
||||||
break;
|
break;
|
||||||
|
case 1:
|
||||||
|
return [GatewayType::CREDIT_CARD => ['refund' => false, 'token_billing' => false]];//Payfast
|
||||||
|
break;
|
||||||
case 15:
|
case 15:
|
||||||
return [GatewayType::PAYPAL => ['refund' => true, 'token_billing' => false]]; //Paypal
|
return [GatewayType::PAYPAL => ['refund' => true, 'token_billing' => false]]; //Paypal
|
||||||
break;
|
break;
|
||||||
@ -95,6 +98,8 @@ class Gateway extends StaticModel
|
|||||||
case 39:
|
case 39:
|
||||||
return [GatewayType::CREDIT_CARD => ['refund' => true, 'token_billing' => true]]; //Checkout
|
return [GatewayType::CREDIT_CARD => ['refund' => true, 'token_billing' => true]]; //Checkout
|
||||||
break;
|
break;
|
||||||
|
case 46:
|
||||||
|
return [GatewayType::CREDIT_CARD => ['refund' => true, 'token_billing' => true]]; //Paytrace
|
||||||
case 49:
|
case 49:
|
||||||
return [GatewayType::CREDIT_CARD => ['refund' => true, 'token_billing' => true],
|
return [GatewayType::CREDIT_CARD => ['refund' => true, 'token_billing' => true],
|
||||||
GatewayType::BANK_TRANSFER => ['refund' => true, 'token_billing' => true]]; //WePay
|
GatewayType::BANK_TRANSFER => ['refund' => true, 'token_billing' => true]]; //WePay
|
||||||
|
@ -68,7 +68,7 @@ class SystemLog extends Model
|
|||||||
const TYPE_BRAINTREE = 307;
|
const TYPE_BRAINTREE = 307;
|
||||||
const TYPE_WEPAY = 309;
|
const TYPE_WEPAY = 309;
|
||||||
const TYPE_PAYFAST = 310;
|
const TYPE_PAYFAST = 310;
|
||||||
|
const TYPE_PAYTRACE = 311;
|
||||||
|
|
||||||
const TYPE_QUOTA_EXCEEDED = 400;
|
const TYPE_QUOTA_EXCEEDED = 400;
|
||||||
const TYPE_UPSTREAM_FAILURE = 401;
|
const TYPE_UPSTREAM_FAILURE = 401;
|
||||||
|
@ -431,6 +431,61 @@ class BaseDriver extends AbstractPaymentDriver
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*Generic Global unsuccessful transaction method when the client is present*/
|
||||||
|
public function processUnsuccessfulTransaction($response, $client_present = true)
|
||||||
|
{
|
||||||
|
$error = $response['error'];
|
||||||
|
$error_code = $response['error_code'];
|
||||||
|
|
||||||
|
$this->unWindGatewayFees($this->payment_hash);
|
||||||
|
|
||||||
|
PaymentFailureMailer::dispatch($this->client, $error, $this->client->company, $this->payment_hash->data->amount_with_fee);
|
||||||
|
|
||||||
|
$nmo = new NinjaMailerObject;
|
||||||
|
$nmo->mailable = new NinjaMailer( (new ClientPaymentFailureObject($this->client, $error, $this->client->company, $this->payment_hash))->build() );
|
||||||
|
$nmo->company = $this->client->company;
|
||||||
|
$nmo->settings = $this->client->company->settings;
|
||||||
|
|
||||||
|
$invoices = Invoice::whereIn('id', $this->transformKeys(array_column($this->payment_hash->invoices(), 'invoice_id')))->withTrashed()->get();
|
||||||
|
|
||||||
|
$invoices->each(function ($invoice){
|
||||||
|
|
||||||
|
$invoice->service()->deletePdf();
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
$invoices->first()->invitations->each(function ($invitation) use ($nmo){
|
||||||
|
|
||||||
|
if ($invitation->contact->send_email && $invitation->contact->email) {
|
||||||
|
|
||||||
|
$nmo->to_user = $invitation->contact;
|
||||||
|
NinjaMailerJob::dispatch($nmo);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
$message = [
|
||||||
|
'server_response' => $response,
|
||||||
|
'data' => $this->payment_hash->data,
|
||||||
|
];
|
||||||
|
|
||||||
|
SystemLogger::dispatch(
|
||||||
|
$message,
|
||||||
|
SystemLog::CATEGORY_GATEWAY_RESPONSE,
|
||||||
|
SystemLog::EVENT_GATEWAY_FAILURE,
|
||||||
|
$this::SYSTEM_LOG_TYPE,
|
||||||
|
$this->client,
|
||||||
|
$this->client->company,
|
||||||
|
);
|
||||||
|
|
||||||
|
if($client_present)
|
||||||
|
throw new PaymentFailed($error, 500);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public function checkRequirements()
|
public function checkRequirements()
|
||||||
{
|
{
|
||||||
if ($this->company_gateway->require_billing_address) {
|
if ($this->company_gateway->require_billing_address) {
|
||||||
|
258
app/PaymentDrivers/PayTrace/CreditCard.php
Normal file
258
app/PaymentDrivers/PayTrace/CreditCard.php
Normal file
@ -0,0 +1,258 @@
|
|||||||
|
<?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://www.elastic.co/licensing/elastic-license
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace App\PaymentDrivers\PayTrace;
|
||||||
|
|
||||||
|
use App\Exceptions\PaymentFailed;
|
||||||
|
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\PayFastPaymentDriver;
|
||||||
|
use App\PaymentDrivers\PaytracePaymentDriver;
|
||||||
|
use App\Utils\Traits\MakesHash;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\Cache;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
|
||||||
|
class CreditCard
|
||||||
|
{
|
||||||
|
use MakesHash;
|
||||||
|
|
||||||
|
public $paytrace;
|
||||||
|
|
||||||
|
public function __construct(PaytracePaymentDriver $paytrace)
|
||||||
|
{
|
||||||
|
$this->paytrace = $paytrace;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function authorizeView($data)
|
||||||
|
{
|
||||||
|
|
||||||
|
$data['client_key'] = $this->paytrace->getAuthToken();
|
||||||
|
$data['gateway'] = $this->paytrace;
|
||||||
|
|
||||||
|
return render('gateways.paytrace.authorize', $data);
|
||||||
|
}
|
||||||
|
|
||||||
|
// +"success": true
|
||||||
|
// +"response_code": 160
|
||||||
|
// +"status_message": "The customer profile for PLS5U60OoLUfQXzcmtJYNefPA0gTthzT/11 was successfully created."
|
||||||
|
// +"customer_id": "PLS5U60OoLUfQXzcmtJYNefPA0gTthzT"
|
||||||
|
|
||||||
|
//if(!$response->success)
|
||||||
|
//handle failure
|
||||||
|
|
||||||
|
public function authorizeResponse($request)
|
||||||
|
{
|
||||||
|
$data = $request->all();
|
||||||
|
|
||||||
|
$response = $this->createCustomer($data);
|
||||||
|
|
||||||
|
return redirect()->route('client.payment_methods.index');
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// "_token" => "Vl1xHflBYQt9YFSaNCPTJKlY5x3rwcFE9kvkw71I"
|
||||||
|
// "company_gateway_id" => "1"
|
||||||
|
// "HPF_Token" => "e484a92c-90ed-4468-ac4d-da66824c75de"
|
||||||
|
// "enc_key" => "zqz6HMHCXALWdX5hyBqrIbSwU7TBZ0FTjjLB3Cp0FQY="
|
||||||
|
// "amount" => "Amount"
|
||||||
|
// "q" => "/client/payment_methods"
|
||||||
|
// "method" => "1"
|
||||||
|
// ]
|
||||||
|
|
||||||
|
// "customer_id":"customer789",
|
||||||
|
// "hpf_token":"e369847e-3027-4174-9161-fa0d4e98d318",
|
||||||
|
// "enc_key":"lI785yOBMet4Rt9o4NLXEyV84WBU3tdStExcsfoaOoo=",
|
||||||
|
// "integrator_id":"xxxxxxxxxx",
|
||||||
|
// "billing_address":{
|
||||||
|
// "name":"Mark Smith",
|
||||||
|
// "street_address":"8320 E. West St.",
|
||||||
|
// "city":"Spokane",
|
||||||
|
// "state":"WA",
|
||||||
|
// "zip":"85284"
|
||||||
|
// }
|
||||||
|
|
||||||
|
private function createCustomer($data)
|
||||||
|
{
|
||||||
|
$post_data = [
|
||||||
|
'customer_id' => Str::random(32),
|
||||||
|
'hpf_token' => $data['HPF_Token'],
|
||||||
|
'enc_key' => $data['enc_key'],
|
||||||
|
'integrator_id' => $this->paytrace->company_gateway->getConfigField('integratorId'),
|
||||||
|
'billing_address' => $this->buildBillingAddress(),
|
||||||
|
];
|
||||||
|
|
||||||
|
$response = $this->paytrace->gatewayRequest('/v1/customer/pt_protect_create', $post_data);
|
||||||
|
|
||||||
|
$cgt = [];
|
||||||
|
$cgt['token'] = $response->customer_id;
|
||||||
|
$cgt['payment_method_id'] = GatewayType::CREDIT_CARD;
|
||||||
|
|
||||||
|
$profile = $this->getCustomerProfile($response->customer_id);
|
||||||
|
|
||||||
|
$payment_meta = new \stdClass;
|
||||||
|
$payment_meta->exp_month = $profile->credit_card->expiration_month;
|
||||||
|
$payment_meta->exp_year = $profile->credit_card->expiration_year;
|
||||||
|
$payment_meta->brand = 'CC';
|
||||||
|
$payment_meta->last4 = $profile->credit_card->masked_number;
|
||||||
|
$payment_meta->type = GatewayType::CREDIT_CARD;
|
||||||
|
|
||||||
|
$cgt['payment_meta'] = $payment_meta;
|
||||||
|
|
||||||
|
$token = $this->paytrace->storeGatewayToken($cgt, []);
|
||||||
|
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getCustomerProfile($customer_id)
|
||||||
|
{
|
||||||
|
$profile = $this->paytrace->gatewayRequest('/v1/customer/export', [
|
||||||
|
'integrator_id' => $this->paytrace->company_gateway->getConfigField('integratorId'),
|
||||||
|
'customer_id' => $customer_id,
|
||||||
|
// 'include_bin' => true,
|
||||||
|
]);
|
||||||
|
|
||||||
|
return $profile->customers[0];
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private function buildBillingAddress()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'name' => $this->paytrace->client->present()->name(),
|
||||||
|
'street_address' => $this->paytrace->client->address1,
|
||||||
|
'city' => $this->paytrace->client->city,
|
||||||
|
'state' => $this->paytrace->client->state,
|
||||||
|
'zip' => $this->paytrace->client->postal_code
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function paymentView($data)
|
||||||
|
{
|
||||||
|
|
||||||
|
$data['client_key'] = $this->paytrace->getAuthToken();
|
||||||
|
$data['gateway'] = $this->paytrace;
|
||||||
|
|
||||||
|
return render('gateways.paytrace.pay', $data);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public function paymentResponse(Request $request)
|
||||||
|
{
|
||||||
|
$response_array = $request->all();
|
||||||
|
|
||||||
|
if($request->token){
|
||||||
|
$token = ClientGatewayToken::find($this->decodePrimaryKey($request->token));
|
||||||
|
return $this->processTokenPayment($token->token, $request);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($request->has('store_card') && $request->input('store_card') === true) {
|
||||||
|
|
||||||
|
$response = $this->createCustomer($request->all());
|
||||||
|
|
||||||
|
return $this->processTokenPayment($response->customer_id, $request);
|
||||||
|
}
|
||||||
|
|
||||||
|
//process a regular charge here:
|
||||||
|
$data = [
|
||||||
|
'hpf_token' => $response_array['HPF_Token'],
|
||||||
|
'enc_key' => $response_array['enc_key'],
|
||||||
|
'integrator_id' => $this->paytrace->company_gateway->getConfigField('integratorId'),
|
||||||
|
'billing_address' => $this->buildBillingAddress(),
|
||||||
|
'amount' => $request->input('amount_with_fee'),
|
||||||
|
'invoice_id' => $this->harvestInvoiceId(),
|
||||||
|
];
|
||||||
|
|
||||||
|
$response = $this->paytrace->gatewayRequest('/v1/transactions/sale/pt_protect', $data);
|
||||||
|
|
||||||
|
if($response->success)
|
||||||
|
return $this->processSuccessfulPayment($response);
|
||||||
|
|
||||||
|
return $this->processUnsuccessfulPayment($response);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public function processTokenPayment($token, $request)
|
||||||
|
{
|
||||||
|
|
||||||
|
$data = [
|
||||||
|
'customer_id' => $token,
|
||||||
|
'integrator_id' => $this->paytrace->company_gateway->getConfigField('integratorId'),
|
||||||
|
'amount' => $request->input('amount_with_fee'),
|
||||||
|
];
|
||||||
|
|
||||||
|
$response = $this->paytrace->gatewayRequest('/v1/transactions/sale/by_customer', $data);
|
||||||
|
|
||||||
|
if($response->success){
|
||||||
|
$this->paytrace->logSuccessfulGatewayResponse(['response' => $response, 'data' => $this->paytrace->payment_hash], SystemLog::TYPE_PAYTRACE);
|
||||||
|
|
||||||
|
return $this->processSuccessfulPayment($response);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->processUnsuccessfulPayment($response);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function harvestInvoiceId()
|
||||||
|
{
|
||||||
|
$_invoice = collect($this->paytrace->payment_hash->data->invoices)->first();
|
||||||
|
$invoice = Invoice::withTrashed()->find($this->decodePrimaryKey($_invoice->invoice_id));
|
||||||
|
|
||||||
|
if($invoice)
|
||||||
|
return ctrans('texts.invoice_number') . "# " . $invoice->number;
|
||||||
|
|
||||||
|
return ctrans('texts.invoice_number') . "####";
|
||||||
|
}
|
||||||
|
|
||||||
|
private function processSuccessfulPayment($response)
|
||||||
|
{
|
||||||
|
$amount = array_sum(array_column($this->paytrace->payment_hash->invoices(), 'amount')) + $this->paytrace->payment_hash->fee_total;
|
||||||
|
|
||||||
|
$payment_record = [];
|
||||||
|
$payment_record['amount'] = $amount;
|
||||||
|
$payment_record['payment_type'] = PaymentType::CREDIT_CARD_OTHER;
|
||||||
|
$payment_record['gateway_type_id'] = GatewayType::CREDIT_CARD;
|
||||||
|
$payment_record['transaction_reference'] = $response->transaction_id;
|
||||||
|
|
||||||
|
$payment = $this->paytrace->createPayment($payment_record, Payment::STATUS_COMPLETED);
|
||||||
|
|
||||||
|
return redirect()->route('client.payments.show', ['payment' => $this->encodePrimaryKey($payment->id)]);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private function processUnsuccessfulPayment($response)
|
||||||
|
{
|
||||||
|
|
||||||
|
$error = $response->status_message;
|
||||||
|
|
||||||
|
if(property_exists($response, 'approval_message') && $response->approval_message)
|
||||||
|
$error .= " - {$response->approval_message}";
|
||||||
|
|
||||||
|
$error_code = property_exists($response, 'approval_message') ? $response->approval_message : 'Undefined code';
|
||||||
|
|
||||||
|
$data = [
|
||||||
|
'response' => $response,
|
||||||
|
'error' => $error,
|
||||||
|
'error_code' => $error_code,
|
||||||
|
];
|
||||||
|
|
||||||
|
return $this->paytrace->processUnsuccessfulTransaction($data);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
234
app/PaymentDrivers/PaytracePaymentDriver.php
Normal file
234
app/PaymentDrivers/PaytracePaymentDriver.php
Normal file
@ -0,0 +1,234 @@
|
|||||||
|
<?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://www.elastic.co/licensing/elastic-license
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace App\PaymentDrivers;
|
||||||
|
|
||||||
|
use App\Jobs\Util\SystemLogger;
|
||||||
|
use App\Models\ClientGatewayToken;
|
||||||
|
use App\Models\GatewayType;
|
||||||
|
use App\Models\Payment;
|
||||||
|
use App\Models\PaymentHash;
|
||||||
|
use App\Models\PaymentType;
|
||||||
|
use App\Models\SystemLog;
|
||||||
|
use App\PaymentDrivers\PayTrace\CreditCard;
|
||||||
|
use App\Utils\CurlUtils;
|
||||||
|
use App\Utils\Traits\MakesHash;
|
||||||
|
|
||||||
|
class PaytracePaymentDriver extends BaseDriver
|
||||||
|
{
|
||||||
|
use MakesHash;
|
||||||
|
|
||||||
|
public $refundable = true;
|
||||||
|
|
||||||
|
public $token_billing = true;
|
||||||
|
|
||||||
|
public $can_authorise_credit_card = true;
|
||||||
|
|
||||||
|
public $gateway;
|
||||||
|
|
||||||
|
public $payment_method;
|
||||||
|
|
||||||
|
public static $methods = [
|
||||||
|
GatewayType::CREDIT_CARD => CreditCard::class, //maps GatewayType => Implementation class
|
||||||
|
];
|
||||||
|
|
||||||
|
const SYSTEM_LOG_TYPE = SystemLog::TYPE_PAYTRACE; //define a constant for your gateway ie TYPE_YOUR_CUSTOM_GATEWAY - set the const in the SystemLog model
|
||||||
|
|
||||||
|
public function init()
|
||||||
|
{
|
||||||
|
return $this; /* This is where you boot the gateway with your auth credentials*/
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Returns an array of gateway types for the payment gateway */
|
||||||
|
public function gatewayTypes(): array
|
||||||
|
{
|
||||||
|
$types = [];
|
||||||
|
|
||||||
|
$types[] = GatewayType::CREDIT_CARD;
|
||||||
|
|
||||||
|
return $types;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Sets the payment method initialized */
|
||||||
|
public function setPaymentMethod($payment_method_id)
|
||||||
|
{
|
||||||
|
$class = self::$methods[$payment_method_id];
|
||||||
|
$this->payment_method = new $class($this);
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function authorizeView(array $data)
|
||||||
|
{
|
||||||
|
return $this->payment_method->authorizeView($data); //this is your custom implementation from here
|
||||||
|
}
|
||||||
|
|
||||||
|
public function authorizeResponse($request)
|
||||||
|
{
|
||||||
|
return $this->payment_method->authorizeResponse($request); //this is your custom implementation from here
|
||||||
|
}
|
||||||
|
|
||||||
|
public function processPaymentView(array $data)
|
||||||
|
{
|
||||||
|
return $this->payment_method->paymentView($data); //this is your custom implementation from here
|
||||||
|
}
|
||||||
|
|
||||||
|
public function processPaymentResponse($request)
|
||||||
|
{
|
||||||
|
return $this->payment_method->paymentResponse($request); //this is your custom implementation from here
|
||||||
|
}
|
||||||
|
|
||||||
|
public function refund(Payment $payment, $amount, $return_client_response = false)
|
||||||
|
{
|
||||||
|
// $cgt = ClientGatewayToken::where('company_gateway_id', $payment->company_gateway_id)
|
||||||
|
// ->where('gateway_type_id', $payment->gateway_type_id)
|
||||||
|
// ->first();
|
||||||
|
|
||||||
|
$data = [
|
||||||
|
'amount' => $amount,
|
||||||
|
//'customer_id' => $cgt->token,
|
||||||
|
'transaction_id' => $payment->transaction_reference,
|
||||||
|
'integrator_id' => '959195xd1CuC'
|
||||||
|
];
|
||||||
|
|
||||||
|
$response = $this->gatewayRequest('/v1/transactions/refund/for_transaction', $data);
|
||||||
|
|
||||||
|
if($response && $response->success)
|
||||||
|
{
|
||||||
|
|
||||||
|
SystemLogger::dispatch(['server_response' => $response, 'data' => $data], SystemLog::CATEGORY_GATEWAY_RESPONSE, SystemLog::EVENT_GATEWAY_SUCCESS, SystemLog::TYPE_PAYTRACE, $this->client, $this->client->company);
|
||||||
|
|
||||||
|
return [
|
||||||
|
'transaction_reference' => $response->transaction_id,
|
||||||
|
'transaction_response' => json_encode($response),
|
||||||
|
'success' => true,
|
||||||
|
'description' => $response->status_message,
|
||||||
|
'code' => $response->response_code,
|
||||||
|
];
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
SystemLogger::dispatch(['server_response' => $response, 'data' => $data], SystemLog::CATEGORY_GATEWAY_RESPONSE, SystemLog::EVENT_GATEWAY_FAILURE, SystemLog::TYPE_PAYTRACE, $this->client, $this->client->company);
|
||||||
|
|
||||||
|
return [
|
||||||
|
'transaction_reference' => null,
|
||||||
|
'transaction_response' => json_encode($response),
|
||||||
|
'success' => false,
|
||||||
|
'description' => $response->status_message,
|
||||||
|
'code' => 422,
|
||||||
|
];
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public function tokenBilling(ClientGatewayToken $cgt, PaymentHash $payment_hash)
|
||||||
|
{
|
||||||
|
$amount = array_sum(array_column($payment_hash->invoices(), 'amount')) + $payment_hash->fee_total;
|
||||||
|
|
||||||
|
$data = [
|
||||||
|
'customer_id' => $cgt->token,
|
||||||
|
'integrator_id' => $this->company_gateway->getConfigField('integratorId'),
|
||||||
|
'amount' => $amount,
|
||||||
|
];
|
||||||
|
|
||||||
|
$response = $this->gatewayRequest('/v1/transactions/sale/by_customer', $data);
|
||||||
|
|
||||||
|
if($response && $response->success)
|
||||||
|
{
|
||||||
|
$data = [
|
||||||
|
'gateway_type_id' => $cgt->gateway_type_id,
|
||||||
|
'payment_type' => PaymentType::CREDIT_CARD_OTHER,
|
||||||
|
'transaction_reference' => $response->transaction_id,
|
||||||
|
'amount' => $amount,
|
||||||
|
];
|
||||||
|
|
||||||
|
$payment = $this->createPayment($data);
|
||||||
|
$payment->meta = $cgt->meta;
|
||||||
|
$payment->save();
|
||||||
|
|
||||||
|
$payment_hash->payment_id = $payment->id;
|
||||||
|
$payment_hash->save();
|
||||||
|
|
||||||
|
return $payment;
|
||||||
|
}
|
||||||
|
|
||||||
|
$error = $response->status_message;
|
||||||
|
|
||||||
|
if(property_exists($response, 'approval_message') && $response->approval_message)
|
||||||
|
$error .= " - {$response->approval_message}";
|
||||||
|
|
||||||
|
$data = [
|
||||||
|
'response' => $response,
|
||||||
|
'error' => $error,
|
||||||
|
'error_code' => 500,
|
||||||
|
];
|
||||||
|
|
||||||
|
$this->processUnsuccessfulTransaction($data, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function processWebhookRequest(PaymentWebhookRequest $request, Payment $payment = null)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/*Helpers*/
|
||||||
|
private function generateAuthHeaders()
|
||||||
|
{
|
||||||
|
|
||||||
|
$url = 'https://api.paytrace.com/oauth/token';
|
||||||
|
$data = [
|
||||||
|
'grant_type' => 'password',
|
||||||
|
'username' => $this->company_gateway->getConfigField('username'),
|
||||||
|
'password' => $this->company_gateway->getConfigField('password')
|
||||||
|
];
|
||||||
|
|
||||||
|
$response = CurlUtils::post($url, $data, $headers = false);
|
||||||
|
|
||||||
|
$auth_data = json_decode($response);
|
||||||
|
|
||||||
|
$headers = [];
|
||||||
|
$headers[] = 'Content-type: application/json';
|
||||||
|
$headers[] = 'Authorization: Bearer '.$auth_data->access_token;
|
||||||
|
|
||||||
|
return $headers;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getAuthToken()
|
||||||
|
{
|
||||||
|
|
||||||
|
$headers = $this->generateAuthHeaders();
|
||||||
|
|
||||||
|
$response = CurlUtils::post('https://api.paytrace.com/v1/payment_fields/token/create', [], $headers);
|
||||||
|
|
||||||
|
$response = json_decode($response);
|
||||||
|
|
||||||
|
if($response)
|
||||||
|
return $response->clientKey;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function gatewayRequest($uri, $data, $headers = false)
|
||||||
|
{
|
||||||
|
|
||||||
|
$base_url = "https://api.paytrace.com{$uri}";
|
||||||
|
|
||||||
|
$headers = $this->generateAuthHeaders();
|
||||||
|
|
||||||
|
$response = CurlUtils::post($base_url, json_encode($data), $headers);
|
||||||
|
|
||||||
|
$response = json_decode($response);
|
||||||
|
|
||||||
|
if($response)
|
||||||
|
return $response;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@ -74,7 +74,7 @@ class Charge
|
|||||||
'confirm' => true,
|
'confirm' => true,
|
||||||
'description' => $description,
|
'description' => $description,
|
||||||
];
|
];
|
||||||
nlog($data);
|
|
||||||
$response = $this->stripe->createPaymentIntent($data, $this->stripe->stripe_connect_auth);
|
$response = $this->stripe->createPaymentIntent($data, $this->stripe->stripe_connect_auth);
|
||||||
// $response = $local_stripe->paymentIntents->create($data);
|
// $response = $local_stripe->paymentIntents->create($data);
|
||||||
|
|
||||||
|
@ -84,6 +84,11 @@ return [
|
|||||||
'test_email' => env('TEST_EMAIL', 'test@example.com'),
|
'test_email' => env('TEST_EMAIL', 'test@example.com'),
|
||||||
'wepay' => env('WEPAY_KEYS', ''),
|
'wepay' => env('WEPAY_KEYS', ''),
|
||||||
'braintree' => env('BRAINTREE_KEYS', ''),
|
'braintree' => env('BRAINTREE_KEYS', ''),
|
||||||
|
'paytrace' => [
|
||||||
|
'username' => env('PAYTRACE_U', ''),
|
||||||
|
'password' => env('PAYTRACE_P',''),
|
||||||
|
'decrypted' => env('PAYTRACE_KEYS', ''),
|
||||||
|
],
|
||||||
],
|
],
|
||||||
'contact' => [
|
'contact' => [
|
||||||
'email' => env('MAIL_FROM_ADDRESS'),
|
'email' => env('MAIL_FROM_ADDRESS'),
|
||||||
|
@ -0,0 +1,39 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use App\Models\Gateway;
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
class ActivatePaytracePaymentDriver extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function up()
|
||||||
|
{
|
||||||
|
|
||||||
|
if($paytrace = Gateway::find(46))
|
||||||
|
{
|
||||||
|
$fields = json_decode($paytrace->fields);
|
||||||
|
$fields->integratorId = "";
|
||||||
|
|
||||||
|
$paytrace->fields = json_encode($fields);
|
||||||
|
$paytrace->provider = 'Paytrace';
|
||||||
|
$paytrace->visible = true;
|
||||||
|
$paytrace->save();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function down()
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
||||||
|
}
|
@ -70,7 +70,7 @@ class PaymentLibrariesSeeder extends Seeder
|
|||||||
['id' => 43, 'name' => 'Fasapay', 'provider' => 'Fasapay', 'key' => '1b2cef0e8c800204a29f33953aaf3360', 'fields' => ''],
|
['id' => 43, 'name' => 'Fasapay', 'provider' => 'Fasapay', 'key' => '1b2cef0e8c800204a29f33953aaf3360', 'fields' => ''],
|
||||||
['id' => 44, 'name' => 'Komoju', 'provider' => 'Komoju', 'key' => '7ea2d40ecb1eb69ef8c3d03e5019028a', 'fields' => '{"apiKey":"","accountId":"","paymentMethod":"credit_card","testMode":false,"locale":"en"}'],
|
['id' => 44, 'name' => 'Komoju', 'provider' => 'Komoju', 'key' => '7ea2d40ecb1eb69ef8c3d03e5019028a', 'fields' => '{"apiKey":"","accountId":"","paymentMethod":"credit_card","testMode":false,"locale":"en"}'],
|
||||||
['id' => 45, 'name' => 'Paysafecard', 'provider' => 'Paysafecard', 'key' => '70ab90cd6c5c1ab13208b3cef51c0894', 'fields' => '{"username":"","password":"","testMode":false}'],
|
['id' => 45, 'name' => 'Paysafecard', 'provider' => 'Paysafecard', 'key' => '70ab90cd6c5c1ab13208b3cef51c0894', 'fields' => '{"username":"","password":"","testMode":false}'],
|
||||||
['id' => 46, 'name' => 'Paytrace', 'provider' => 'Paytrace_CreditCard', 'key' => 'bbd736b3254b0aabed6ad7fda1298c88', 'fields' => '{"username":"","password":"","testMode":false,"endpoint":"https:\/\/paytrace.com\/api\/default.pay"}'],
|
['id' => 46, 'name' => 'Paytrace', 'provider' => 'Paytrace', 'key' => 'bbd736b3254b0aabed6ad7fda1298c88', 'fields' => '{"username":"","password":"","integratorId":"","testMode":false,"endpoint":"https:\/\/paytrace.com\/api\/default.pay"}'],
|
||||||
['id' => 47, 'name' => 'Secure Trading', 'provider' => 'SecureTrading', 'key' => '231cb401487b9f15babe04b1ac4f7a27', 'fields' => '{"siteReference":"","username":"","password":"","applyThreeDSecure":false,"accountType":"ECOM"}'],
|
['id' => 47, 'name' => 'Secure Trading', 'provider' => 'SecureTrading', 'key' => '231cb401487b9f15babe04b1ac4f7a27', 'fields' => '{"siteReference":"","username":"","password":"","applyThreeDSecure":false,"accountType":"ECOM"}'],
|
||||||
['id' => 48, 'name' => 'SecPay', 'provider' => 'SecPay', 'key' => 'bad8699d581d9fa040e59c0bb721a76c', 'fields' => '{"mid":"","vpnPswd":"","remotePswd":"","usageType":"","confirmEmail":"","testStatus":"true","mailCustomer":"true","additionalOptions":""}'],
|
['id' => 48, 'name' => 'SecPay', 'provider' => 'SecPay', 'key' => 'bad8699d581d9fa040e59c0bb721a76c', 'fields' => '{"mid":"","vpnPswd":"","remotePswd":"","usageType":"","confirmEmail":"","testStatus":"true","mailCustomer":"true","additionalOptions":""}'],
|
||||||
['id' => 49, 'name' => 'WePay', 'provider' => 'WePay', 'is_offsite' => false, 'sort_order' => 3, 'key' => '8fdeed552015b3c7b44ed6c8ebd9e992', 'fields' => '{"accountId":"","accessToken":"","type":"goods","testMode":false,"feePayer":"payee"}'],
|
['id' => 49, 'name' => 'WePay', 'provider' => 'WePay', 'is_offsite' => false, 'sort_order' => 3, 'key' => '8fdeed552015b3c7b44ed6c8ebd9e992', 'fields' => '{"accountId":"","accessToken":"","type":"goods","testMode":false,"feePayer":"payee"}'],
|
||||||
@ -96,7 +96,7 @@ class PaymentLibrariesSeeder extends Seeder
|
|||||||
|
|
||||||
Gateway::query()->update(['visible' => 0]);
|
Gateway::query()->update(['visible' => 0]);
|
||||||
|
|
||||||
Gateway::whereIn('id', [1,15,20,39,55,50])->update(['visible' => 1]);
|
Gateway::whereIn('id', [1,15,20,39,46,55,50])->update(['visible' => 1]);
|
||||||
|
|
||||||
if (Ninja::isHosted()) {
|
if (Ninja::isHosted()) {
|
||||||
Gateway::whereIn('id', [20])->update(['visible' => 0]);
|
Gateway::whereIn('id', [20])->update(['visible' => 0]);
|
||||||
|
2
public/css/app.css
vendored
2
public/css/app.css
vendored
File diff suppressed because one or more lines are too long
2
public/js/clients/payments/paytrace-credit-card.js
vendored
Normal file
2
public/js/clients/payments/paytrace-credit-card.js
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
/*! For license information please see paytrace-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=21)}({"0Swb":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(){var t;!function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}(this,e),this.clientKey=null===(t=document.querySelector("meta[name=paytrace-client-key]"))||void 0===t?void 0:t.content}var t,r,o;return t=e,(r=[{key:"creditCardStyles",get:function(){return{font_color:"#111827",border_color:"rgba(210,214,220,1)",label_color:"#111827",label_size:"12pt",background_color:"white",border_style:"solid",font_size:"15pt",height:"30px",width:"100%"}}},{key:"codeStyles",get:function(){return{font_color:"#111827",border_color:"rgba(210,214,220,1)",label_color:"#111827",label_size:"12pt",background_color:"white",border_style:"solid",font_size:"15pt",height:"30px",width:"300px"}}},{key:"expStyles",get:function(){return{font_color:"#111827",border_color:"rgba(210,214,220,1)",label_color:"#111827",label_size:"12pt",background_color:"white",border_style:"solid",font_size:"15pt",height:"30px",width:"85px",type:"dropdown"}}},{key:"updatePayTraceLabels",value:function(){window.PTPayment.getControl("securityCode").label.text(document.querySelector("meta[name=ctrans-cvv]").content),window.PTPayment.getControl("creditCard").label.text(document.querySelector("meta[name=ctrans-card_number]").content),window.PTPayment.getControl("expiration").label.text(document.querySelector("meta[name=ctrans-expires]").content)}},{key:"setupPayTrace",value:function(){return window.PTPayment.setup({styles:{code:this.codeStyles,cc:this.creditCardStyles,exp:this.expStyles},authorization:{clientKey:this.clientKey}})}},{key:"handlePaymentWithCreditCard",value:function(e){var t=this;e.target.parentElement.disabled=!0,document.getElementById("errors").hidden=!0,window.PTPayment.validate((function(n){if(n.length>=1){var r=document.getElementById("errors");return r.textContent=n[0].description,r.hidden=!1,e.target.parentElement.disabled=!1}t.ptInstance.process().then((function(e){document.getElementById("HPF_Token").value=e.message.hpf_token,document.getElementById("enc_key").value=e.message.enc_key;var t=document.querySelector('input[name="token-billing-checkbox"]:checked');t&&(document.querySelector('input[name="store_card"]').value=t.value),document.getElementById("server_response").submit()})).catch((function(e){document.getElementById("errors").textContent=JSON.stringify(e),document.getElementById("errors").hidden=!1,console.log(e)}))}))}},{key:"handlePaymentWithToken",value:function(e){e.target.parentElement.disabled=!0,document.getElementById("server_response").submit()}},{key:"handle",value:function(){var e=this;this.setupPayTrace().then((function(t){var n;e.ptInstance=t,e.updatePayTraceLabels(),Array.from(document.getElementsByClassName("toggle-payment-with-token")).forEach((function(e){return e.addEventListener("click",(function(e){document.getElementById("paytrace--credit-card-container").classList.add("hidden"),document.getElementById("save-card--container").style.display="none",document.querySelector("input[name=token]").value=e.target.dataset.token}))})),null===(n=document.getElementById("toggle-payment-with-credit-card"))||void 0===n||n.addEventListener("click",(function(e){document.getElementById("paytrace--credit-card-container").classList.remove("hidden"),document.getElementById("save-card--container").style.display="grid",document.querySelector("input[name=token]").value=""})),document.getElementById("pay-now").addEventListener("click",(function(t){return""===document.querySelector("input[name=token]").value?e.handlePaymentWithCreditCard(t):e.handlePaymentWithToken(t)}))}))}}])&&n(t.prototype,r),o&&n(t,o),e}())).handle()},21:function(e,t,n){e.exports=n("0Swb")}});
|
@ -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://www.elastic.co/licensing/elastic-license
|
||||||
|
*/
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"/js/app.js": "/js/app.js?id=696e8203d5e8e7cf5ff5",
|
"/js/app.js": "/js/app.js?id=696e8203d5e8e7cf5ff5",
|
||||||
"/css/app.css": "/css/app.css?id=f4c07fdabcbe50c9f4be",
|
"/css/app.css": "/css/app.css?id=5d7e42fa72eef8af62f5",
|
||||||
"/js/clients/invoices/action-selectors.js": "/js/clients/invoices/action-selectors.js?id=a09bb529b8e1826f13b4",
|
"/js/clients/invoices/action-selectors.js": "/js/clients/invoices/action-selectors.js?id=a09bb529b8e1826f13b4",
|
||||||
"/js/clients/invoices/payment.js": "/js/clients/invoices/payment.js?id=8ce8955ba775ea5f47d1",
|
"/js/clients/invoices/payment.js": "/js/clients/invoices/payment.js?id=8ce8955ba775ea5f47d1",
|
||||||
"/js/clients/linkify-urls.js": "/js/clients/linkify-urls.js?id=0dc8c34010d09195d2f7",
|
"/js/clients/linkify-urls.js": "/js/clients/linkify-urls.js?id=0dc8c34010d09195d2f7",
|
||||||
@ -11,6 +11,7 @@
|
|||||||
"/js/clients/payments/braintree-paypal.js": "/js/clients/payments/braintree-paypal.js?id=c35db3cbb65806ab6a8a",
|
"/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/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=065e5450233cc5b47020",
|
"/js/clients/payments/checkout-credit-card.js": "/js/clients/payments/checkout-credit-card.js?id=065e5450233cc5b47020",
|
||||||
|
"/js/clients/payments/paytrace-credit-card.js": "/js/clients/payments/paytrace-credit-card.js?id=c8d3808a4c02d1392e96",
|
||||||
"/js/clients/payments/stripe-ach.js": "/js/clients/payments/stripe-ach.js?id=81c2623fc1e5769b51c7",
|
"/js/clients/payments/stripe-ach.js": "/js/clients/payments/stripe-ach.js?id=81c2623fc1e5769b51c7",
|
||||||
"/js/clients/payments/stripe-alipay.js": "/js/clients/payments/stripe-alipay.js?id=665ddf663500767f1a17",
|
"/js/clients/payments/stripe-alipay.js": "/js/clients/payments/stripe-alipay.js?id=665ddf663500767f1a17",
|
||||||
"/js/clients/payments/stripe-credit-card.js": "/js/clients/payments/stripe-credit-card.js?id=a30464874dee84678344",
|
"/js/clients/payments/stripe-credit-card.js": "/js/clients/payments/stripe-credit-card.js?id=a30464874dee84678344",
|
||||||
|
186
resources/js/clients/payments/paytrace-credit-card.js
vendored
Normal file
186
resources/js/clients/payments/paytrace-credit-card.js
vendored
Normal file
@ -0,0 +1,186 @@
|
|||||||
|
/**
|
||||||
|
* 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://www.elastic.co/licensing/elastic-license
|
||||||
|
*/
|
||||||
|
|
||||||
|
class PayTraceCreditCard {
|
||||||
|
constructor() {
|
||||||
|
this.clientKey = document.querySelector(
|
||||||
|
'meta[name=paytrace-client-key]'
|
||||||
|
)?.content;
|
||||||
|
}
|
||||||
|
|
||||||
|
get creditCardStyles() {
|
||||||
|
return {
|
||||||
|
font_color: '#111827',
|
||||||
|
border_color: 'rgba(210,214,220,1)',
|
||||||
|
label_color: '#111827',
|
||||||
|
label_size: '12pt',
|
||||||
|
background_color: 'white',
|
||||||
|
border_style: 'solid',
|
||||||
|
font_size: '15pt',
|
||||||
|
height: '30px',
|
||||||
|
width: '100%',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
get codeStyles() {
|
||||||
|
return {
|
||||||
|
font_color: '#111827',
|
||||||
|
border_color: 'rgba(210,214,220,1)',
|
||||||
|
label_color: '#111827',
|
||||||
|
label_size: '12pt',
|
||||||
|
background_color: 'white',
|
||||||
|
border_style: 'solid',
|
||||||
|
font_size: '15pt',
|
||||||
|
height: '30px',
|
||||||
|
width: '300px',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
get expStyles() {
|
||||||
|
return {
|
||||||
|
font_color: '#111827',
|
||||||
|
border_color: 'rgba(210,214,220,1)',
|
||||||
|
label_color: '#111827',
|
||||||
|
label_size: '12pt',
|
||||||
|
background_color: 'white',
|
||||||
|
border_style: 'solid',
|
||||||
|
font_size: '15pt',
|
||||||
|
height: '30px',
|
||||||
|
width: '85px',
|
||||||
|
type: 'dropdown',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
updatePayTraceLabels() {
|
||||||
|
window.PTPayment.getControl('securityCode').label.text(
|
||||||
|
document.querySelector('meta[name=ctrans-cvv]').content
|
||||||
|
);
|
||||||
|
|
||||||
|
window.PTPayment.getControl('creditCard').label.text(
|
||||||
|
document.querySelector('meta[name=ctrans-card_number]').content
|
||||||
|
);
|
||||||
|
|
||||||
|
window.PTPayment.getControl('expiration').label.text(
|
||||||
|
document.querySelector('meta[name=ctrans-expires]').content
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
setupPayTrace() {
|
||||||
|
return window.PTPayment.setup({
|
||||||
|
styles: {
|
||||||
|
code: this.codeStyles,
|
||||||
|
cc: this.creditCardStyles,
|
||||||
|
exp: this.expStyles,
|
||||||
|
},
|
||||||
|
authorization: {
|
||||||
|
clientKey: this.clientKey,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
handlePaymentWithCreditCard(event) {
|
||||||
|
event.target.parentElement.disabled = true;
|
||||||
|
document.getElementById('errors').hidden = true;
|
||||||
|
|
||||||
|
window.PTPayment.validate((errors) => {
|
||||||
|
if (errors.length >= 1) {
|
||||||
|
let errorsContainer = document.getElementById('errors');
|
||||||
|
|
||||||
|
errorsContainer.textContent = errors[0].description;
|
||||||
|
errorsContainer.hidden = false;
|
||||||
|
|
||||||
|
return (event.target.parentElement.disabled = false);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.ptInstance
|
||||||
|
.process()
|
||||||
|
.then((response) => {
|
||||||
|
document.getElementById('HPF_Token').value =
|
||||||
|
response.message.hpf_token;
|
||||||
|
document.getElementById('enc_key').value =
|
||||||
|
response.message.enc_key;
|
||||||
|
|
||||||
|
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();
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
document.getElementById(
|
||||||
|
'errors'
|
||||||
|
).textContent = JSON.stringify(error);
|
||||||
|
document.getElementById('errors').hidden = false;
|
||||||
|
|
||||||
|
console.log(error);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
handlePaymentWithToken(event) {
|
||||||
|
event.target.parentElement.disabled = true;
|
||||||
|
|
||||||
|
document.getElementById('server_response').submit();
|
||||||
|
}
|
||||||
|
|
||||||
|
handle() {
|
||||||
|
this.setupPayTrace().then((instance) => {
|
||||||
|
this.ptInstance = instance;
|
||||||
|
this.updatePayTraceLabels();
|
||||||
|
|
||||||
|
Array.from(
|
||||||
|
document.getElementsByClassName('toggle-payment-with-token')
|
||||||
|
).forEach((element) =>
|
||||||
|
element.addEventListener('click', (element) => {
|
||||||
|
document
|
||||||
|
.getElementById('paytrace--credit-card-container')
|
||||||
|
.classList.add('hidden');
|
||||||
|
document.getElementById(
|
||||||
|
'save-card--container'
|
||||||
|
).style.display = 'none';
|
||||||
|
document.querySelector('input[name=token]').value =
|
||||||
|
element.target.dataset.token;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
document
|
||||||
|
.getElementById('toggle-payment-with-credit-card')
|
||||||
|
?.addEventListener('click', (element) => {
|
||||||
|
document
|
||||||
|
.getElementById('paytrace--credit-card-container')
|
||||||
|
.classList.remove('hidden');
|
||||||
|
document.getElementById(
|
||||||
|
'save-card--container'
|
||||||
|
).style.display = 'grid';
|
||||||
|
document.querySelector('input[name=token]').value = '';
|
||||||
|
});
|
||||||
|
|
||||||
|
document
|
||||||
|
.getElementById('pay-now')
|
||||||
|
.addEventListener('click', (e) => {
|
||||||
|
if (
|
||||||
|
document.querySelector('input[name=token]').value === ''
|
||||||
|
) {
|
||||||
|
return this.handlePaymentWithCreditCard(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.handlePaymentWithToken(e);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
new PayTraceCreditCard().handle();
|
@ -16,12 +16,12 @@
|
|||||||
value="true"/>
|
value="true"/>
|
||||||
<span class="ml-1 cursor-pointer">{{ ctrans('texts.yes') }}</span>
|
<span class="ml-1 cursor-pointer">{{ ctrans('texts.yes') }}</span>
|
||||||
</label>
|
</label>
|
||||||
<labecoml>
|
<label>
|
||||||
<input type="radio" class="form-radio cursor-pointer" name="token-billing-checkbox"
|
<input type="radio" class="form-radio cursor-pointer" name="token-billing-checkbox"
|
||||||
id="proxy_is_default"
|
id="proxy_is_default"
|
||||||
value="false" checked />
|
value="false" checked />
|
||||||
<span class="ml-1 cursor-pointer">{{ ctrans('texts.no') }}</span>
|
<span class="ml-1 cursor-pointer">{{ ctrans('texts.no') }}</span>
|
||||||
</labecoml>
|
</label>
|
||||||
</dd>
|
</dd>
|
||||||
</div>
|
</div>
|
||||||
@else
|
@else
|
||||||
|
@ -0,0 +1,37 @@
|
|||||||
|
@extends('portal.ninja2020.layout.payments', ['gateway_title' => ctrans('texts.payment_type_credit_card'), 'card_title'
|
||||||
|
=> ctrans('texts.payment_type_credit_card')])
|
||||||
|
|
||||||
|
@section('gateway_head')
|
||||||
|
<meta name="paytrace-client-key" content="{{ $client_key }}">
|
||||||
|
<meta name="ctrans-cvv" content="{{ ctrans('texts.cvv') }}">
|
||||||
|
<meta name="ctrans-card_number" content="{{ ctrans('texts.card_number') }}">
|
||||||
|
<meta name="ctrans-expires" content="{{ ctrans('texts.expires') }}">
|
||||||
|
@endsection
|
||||||
|
|
||||||
|
@section('gateway_content')
|
||||||
|
<form action="{{ route('client.payment_methods.store', ['method' => App\Models\GatewayType::CREDIT_CARD]) }}"
|
||||||
|
method="post" id="server_response">
|
||||||
|
@csrf
|
||||||
|
<input type="hidden" name="company_gateway_id" value="{{ $gateway->company_gateway->id }}">
|
||||||
|
<input type="txt" id=HPF_Token name= HPF_Token hidden>
|
||||||
|
<input type="txt" id=enc_key name= enc_key hidden>
|
||||||
|
<input type="text" name="token" hidden>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<div class="alert alert-failure mb-4" hidden id="errors"></div>
|
||||||
|
|
||||||
|
@component('portal.ninja2020.components.general.card-element-single')
|
||||||
|
<div class="w-screen items-center" id="paytrace--credit-card-container">
|
||||||
|
<div id="pt_hpf_form"></div>
|
||||||
|
</div>
|
||||||
|
@endcomponent
|
||||||
|
|
||||||
|
@component('portal.ninja2020.gateways.includes.pay_now')
|
||||||
|
{{ ctrans('texts.add_payment_method') }}
|
||||||
|
@endcomponent
|
||||||
|
@endsection
|
||||||
|
|
||||||
|
@section('gateway_footer')
|
||||||
|
<script src="https://protect.paytrace.com/js/protect.min.js"></script>
|
||||||
|
<script src="{{ asset('js/clients/payments/paytrace-credit-card.js') }}"></script>
|
||||||
|
@endsection
|
@ -0,0 +1,64 @@
|
|||||||
|
@extends('portal.ninja2020.layout.payments', ['gateway_title' => ctrans('texts.payment_type_credit_card'), 'card_title'
|
||||||
|
=> ctrans('texts.payment_type_credit_card')])
|
||||||
|
|
||||||
|
@section('gateway_head')
|
||||||
|
<meta name="paytrace-client-key" content="{{ $client_key }}">
|
||||||
|
<meta name="ctrans-cvv" content="{{ ctrans('texts.cvv') }}">
|
||||||
|
<meta name="ctrans-card_number" content="{{ ctrans('texts.card_number') }}">
|
||||||
|
<meta name="ctrans-expires" content="{{ ctrans('texts.expires') }}">
|
||||||
|
@endsection
|
||||||
|
|
||||||
|
@section('gateway_content')
|
||||||
|
<form action="{{ route('client.payments.response') }}" method="post" id="server_response">
|
||||||
|
@csrf
|
||||||
|
<input type="hidden" name="payment_hash" value="{{ $payment_hash }}">
|
||||||
|
<input type="hidden" name="company_gateway_id" value="{{ $gateway->company_gateway->id }}">
|
||||||
|
<input type="hidden" name="payment_method_id" value="1">
|
||||||
|
<input type="hidden" name="token" id="token" />
|
||||||
|
<input type="hidden" name="store_card" id="store_card" />
|
||||||
|
<input type="hidden" name="amount_with_fee" id="amount_with_fee" value="{{ $total['amount_with_fee'] }}" />
|
||||||
|
<input type="txt" id="HPF_Token" name="HPF_Token" hidden>
|
||||||
|
<input type="txt" id="enc_key" name="enc_key" hidden>
|
||||||
|
</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.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->hashed_id }}" 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 class="w-screen items-center" id="paytrace--credit-card-container">
|
||||||
|
<div id="pt_hpf_form"></div>
|
||||||
|
</div>
|
||||||
|
@endcomponent
|
||||||
|
|
||||||
|
@include('portal.ninja2020.gateways.includes.pay_now')
|
||||||
|
@endsection
|
||||||
|
|
||||||
|
@section('gateway_footer')
|
||||||
|
<script src='https://protect.paytrace.com/js/protect.min.js'></script>
|
||||||
|
<script src="{{ asset('js/clients/payments/paytrace-credit-card.js') }}"></script>
|
||||||
|
@endsection
|
29
resources/views/vendor/livewire/simple-bootstrap.blade.php
vendored
Normal file
29
resources/views/vendor/livewire/simple-bootstrap.blade.php
vendored
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
<div>
|
||||||
|
@if ($paginator->hasPages())
|
||||||
|
<nav>
|
||||||
|
<ul class="pagination">
|
||||||
|
{{-- Previous Page Link --}}
|
||||||
|
@if ($paginator->onFirstPage())
|
||||||
|
<li class="page-item disabled" aria-disabled="true">
|
||||||
|
<span class="page-link">@lang('pagination.previous')</span>
|
||||||
|
</li>
|
||||||
|
@else
|
||||||
|
<li class="page-item">
|
||||||
|
<button type="button" class="page-link" wire:click="previousPage" wire:loading.attr="disabled" rel="prev">@lang('pagination.previous')</button>
|
||||||
|
</li>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
{{-- Next Page Link --}}
|
||||||
|
@if ($paginator->hasMorePages())
|
||||||
|
<li class="page-item">
|
||||||
|
<button type="button" class="page-link" wire:click="nextPage" wire:loading.attr="disabled" rel="next">@lang('pagination.next')</button>
|
||||||
|
</li>
|
||||||
|
@else
|
||||||
|
<li class="page-item disabled" aria-disabled="true">
|
||||||
|
<span class="page-link">@lang('pagination.next')</span>
|
||||||
|
</li>
|
||||||
|
@endif
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
@endif
|
||||||
|
</div>
|
@ -0,0 +1,62 @@
|
|||||||
|
<?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://www.elastic.co/licensing/elastic-license
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Tests\Browser\ClientPortal\Gateways\PayTrace;
|
||||||
|
|
||||||
|
use App\Models\CompanyGateway;
|
||||||
|
use Laravel\Dusk\Browser;
|
||||||
|
use Tests\Browser\Pages\ClientPortal\Login;
|
||||||
|
use Tests\DuskTestCase;
|
||||||
|
|
||||||
|
class CreditCardTest extends DuskTestCase
|
||||||
|
{
|
||||||
|
protected function setUp(): void
|
||||||
|
{
|
||||||
|
parent::setUp();
|
||||||
|
|
||||||
|
foreach (static::$browsers as $browser) {
|
||||||
|
$browser->driver->manage()->deleteAllCookies();
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->disableCompanyGateways();
|
||||||
|
|
||||||
|
CompanyGateway::where('gateway_key', 'bbd736b3254b0aabed6ad7fda1298c88')->restore();
|
||||||
|
|
||||||
|
$this->browse(function (Browser $browser) {
|
||||||
|
$browser
|
||||||
|
->visit(new Login())
|
||||||
|
->auth();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testPayingWithNewCreditCard()
|
||||||
|
{
|
||||||
|
$this->markTestSkipped('Credit card not supported.');
|
||||||
|
|
||||||
|
$this->browse(function (Browser $browser) {
|
||||||
|
$browser
|
||||||
|
->visitRoute('client.invoices.index')
|
||||||
|
->click('@pay-now')
|
||||||
|
->press('Pay Now')
|
||||||
|
->clickLink('Credit Card')
|
||||||
|
->withinFrame('iframe', function (Browser $browser) {
|
||||||
|
$browser
|
||||||
|
->type('CC', '4012000098765439')
|
||||||
|
->select('EXP_MM', '12')
|
||||||
|
->select('EXP_YY', '30')
|
||||||
|
->type('SEC', '999');
|
||||||
|
})
|
||||||
|
->press('Pay Now')
|
||||||
|
->waitForText('Details of the payment', 60);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
4
webpack.mix.js
vendored
4
webpack.mix.js
vendored
@ -81,6 +81,10 @@ mix.js("resources/js/app.js", "public/js")
|
|||||||
.js(
|
.js(
|
||||||
"resources/js/clients/payment_methods/wepay-bank-account.js",
|
"resources/js/clients/payment_methods/wepay-bank-account.js",
|
||||||
"public/js/clients/payment_methods/wepay-bank-account.js"
|
"public/js/clients/payment_methods/wepay-bank-account.js"
|
||||||
|
)
|
||||||
|
.js(
|
||||||
|
"resources/js/clients/payments/paytrace-credit-card.js",
|
||||||
|
"public/js/clients/payments/paytrace-credit-card.js"
|
||||||
);
|
);
|
||||||
|
|
||||||
mix.copyDirectory('node_modules/card-js/card-js.min.css', 'public/css/card-js.min.css');
|
mix.copyDirectory('node_modules/card-js/card-js.min.css', 'public/css/card-js.min.css');
|
||||||
|
Loading…
x
Reference in New Issue
Block a user