Payments refactor

This commit is contained in:
Hillel Coren 2016-06-20 17:14:43 +03:00
parent e2e629141b
commit 5ff69c6f29
72 changed files with 3659 additions and 3224 deletions

View File

@ -51,20 +51,6 @@ class SendRecurringInvoices extends Command
$invoice = $this->invoiceRepo->createRecurringInvoice($recurInvoice);
if ($invoice && !$invoice->isPaid()) {
$invoice->account->auto_bill_on_due_date;
$autoBillLater = false;
if ($invoice->account->auto_bill_on_due_date || $this->paymentService->getClientRequiresDelayedAutoBill($invoice->client)) {
$autoBillLater = true;
}
if($autoBillLater) {
if($paymentMethod = $this->paymentService->getClientDefaultPaymentMethod($invoice->client)) {
$invoice->autoBillPaymentMethod = $paymentMethod;
}
}
$this->info('Sending Invoice');
$this->mailer->sendInvoice($invoice);
}
@ -79,17 +65,12 @@ class SendRecurringInvoices extends Command
$this->info(count($delayedAutoBillInvoices).' due recurring invoice instance(s) found');
foreach ($delayedAutoBillInvoices as $invoice) {
$autoBill = $invoice->getAutoBillEnabled();
$billNow = false;
if ($autoBill && !$invoice->isPaid()) {
$billNow = $invoice->account->auto_bill_on_due_date || $this->paymentService->getClientRequiresDelayedAutoBill($invoice->client);
if ($invoice->isPaid()) {
continue;
}
if ($invoice->getAutoBillEnabled() && $invoice->client->autoBillLater()) {
$this->info('Processing Invoice '.$invoice->id.' - Should bill '.($billNow ? 'YES' : 'NO'));
if ($billNow) {
// autoBillInvoice will check for changes to ACH invoices, so we're not checking here
$this->paymentService->autoBillInvoice($invoice);
}
}

View File

@ -62,7 +62,6 @@ class AccountGatewayController extends BaseController
$data['title'] = trans('texts.edit_gateway') . ' - ' . $accountGateway->gateway->name;
$data['config'] = $config;
$data['hiddenFields'] = Gateway::$hiddenFields;
$data['paymentTypeId'] = $accountGateway->getPaymentType();
$data['selectGateways'] = Gateway::where('id', '=', $accountGateway->gateway_id)->get();
return View::make('accounts.account_gateway', $data);
@ -82,7 +81,7 @@ class AccountGatewayController extends BaseController
* Displays the form for account creation
*
*/
public function create($showWepay = true)
public function create()
{
if ( ! \Request::secure() && ! Utils::isNinjaDev()) {
Session::flash('warning', trans('texts.enable_https'));
@ -90,10 +89,10 @@ class AccountGatewayController extends BaseController
$account = Auth::user()->account;
$accountGatewaysIds = $account->gatewayIds();
$showWepay = filter_var($showWepay, FILTER_VALIDATE_BOOLEAN);
$otherProviders = Input::get('other_providers');
if ( ! Utils::isNinja() || Gateway::hasStandardGateway($accountGatewaysIds)) {
$showWepay = false;
$otherProviders = true;
}
$data = self::getViewModel();
@ -101,14 +100,14 @@ class AccountGatewayController extends BaseController
$data['method'] = 'POST';
$data['title'] = trans('texts.add_gateway');
if ($showWepay) {
return View::make('accounts.account_gateway_wepay', $data);
} else {
if ($otherProviders) {
$data['primaryGateways'] = Gateway::primary($accountGatewaysIds)->orderBy('name', 'desc')->get();
$data['secondaryGateways'] = Gateway::secondary($accountGatewaysIds)->orderBy('name')->get();
$data['hiddenFields'] = Gateway::$hiddenFields;
return View::make('accounts.account_gateway', $data);
} else {
return View::make('accounts.account_gateway_wepay', $data);
}
}

View File

@ -129,6 +129,8 @@ class ClientController extends BaseController
$actionLinks[] = ['label' => trans('texts.enter_expense'), 'url' => URL::to('/expenses/create/0/'.$client->public_id)];
}
$token = $client->getGatewayToken();
$data = array(
'actionLinks' => $actionLinks,
'showBreadcrumbs' => false,
@ -138,8 +140,8 @@ class ClientController extends BaseController
'hasRecurringInvoices' => Invoice::scope()->where('is_recurring', '=', true)->whereClientId($client->id)->count() > 0,
'hasQuotes' => Invoice::scope()->invoiceType(INVOICE_TYPE_QUOTE)->whereClientId($client->id)->count() > 0,
'hasTasks' => Task::scope()->whereClientId($client->id)->count() > 0,
'gatewayLink' => $client->getGatewayLink($accountGateway),
'gateway' => $accountGateway
'gatewayLink' => $token ? $token->gatewayLink() : false,
'gatewayName' => $token ? $token->gatewayName() : false,
);
return View::make('clients.show', $data);

View File

@ -98,7 +98,7 @@ class ClientPortalController extends BaseController
]);
$data = array();
$paymentTypes = $this->getPaymentTypes($client, $invitation);
$paymentTypes = $this->getPaymentTypes($account, $client, $invitation);
$paymentURL = '';
if (count($paymentTypes) == 1) {
$paymentURL = $paymentTypes[0]['url'];
@ -107,11 +107,9 @@ class ClientPortalController extends BaseController
}
}
if ($braintreeGateway = $account->getGatewayConfig(GATEWAY_BRAINTREE)){
if($braintreeGateway->getPayPalEnabled()) {
$data['braintreeClientToken'] = $this->paymentService->getBraintreeClientToken($account);
}
} elseif ($wepayGateway = $account->getGatewayConfig(GATEWAY_WEPAY)){
$paymentDriver = $account->paymentDriver($invitation, GATEWAY_TYPE_CREDIT_CARD);
if ($wepayGateway = $account->getGatewayConfig(GATEWAY_WEPAY)){
$data['enableWePayACH'] = $wepayGateway->getAchEnabled();
}
@ -123,19 +121,6 @@ class ClientPortalController extends BaseController
$showApprove = false;
}
// Checkout.com requires first getting a payment token
$checkoutComToken = false;
$checkoutComKey = false;
$checkoutComDebug = false;
if ($accountGateway = $account->getGatewayConfig(GATEWAY_CHECKOUT_COM)) {
$checkoutComDebug = $accountGateway->getConfigField('testMode');
if ($checkoutComToken = $this->paymentService->getCheckoutComToken($invitation)) {
$checkoutComKey = $accountGateway->getConfigField('publicApiKey');
$invitation->transaction_reference = $checkoutComToken;
$invitation->save();
}
}
$data += array(
'account' => $account,
'showApprove' => $showApprove,
@ -147,9 +132,9 @@ class ClientPortalController extends BaseController
'contact' => $contact,
'paymentTypes' => $paymentTypes,
'paymentURL' => $paymentURL,
'checkoutComToken' => $checkoutComToken,
'checkoutComKey' => $checkoutComKey,
'checkoutComDebug' => $checkoutComDebug,
'transactionToken' => $paymentDriver->createTransactionToken(),
'partialView' => $paymentDriver->partialView(),
'accountGateway' => $paymentDriver->accountGateway,
'phantomjs' => Input::has('phantomjs'),
);
@ -177,95 +162,17 @@ class ClientPortalController extends BaseController
return redirect()->to($client->account->enable_client_portal?'/client/dashboard':'/client/invoices/');
}
private function getPaymentTypes($client, $invitation)
private function getPaymentTypes($account, $client, $invitation)
{
$paymentTypes = [];
$account = $client->account;
$links = [];
$paymentMethods = $this->paymentService->getClientPaymentMethods($client);
if ($paymentMethods) {
foreach ($paymentMethods as $paymentMethod) {
if ($paymentMethod->payment_type_id != PAYMENT_TYPE_ACH || $paymentMethod->status == PAYMENT_METHOD_STATUS_VERIFIED) {
$code = htmlentities(str_replace(' ', '', strtolower($paymentMethod->payment_type->name)));
$html = '';
if ($paymentMethod->payment_type_id == PAYMENT_TYPE_ACH) {
if ($paymentMethod->bank_name) {
$html = '<div>' . htmlentities($paymentMethod->bank_name) . '</div>';
} else {
$html = '<img height="22" src="'.URL::to('/images/credit_cards/ach.png').'" style="float:left" alt="'.trans("texts.direct_debit").'">';
}
} elseif ($paymentMethod->payment_type_id == PAYMENT_TYPE_ID_PAYPAL) {
$html = '<img height="22" src="'.URL::to('/images/credit_cards/paypal.png').'" alt="'.trans("texts.card_".$code).'">';
} else {
$html = '<img height="22" src="'.URL::to('/images/credit_cards/'.$code.'.png').'" alt="'.trans("texts.card_".$code).'">';
foreach ($account->account_gateways as $accountGateway) {
$paymentDriver = $accountGateway->paymentDriver($invitation);
$links = array_merge($links, $paymentDriver->tokenLinks());
$links = array_merge($links, $paymentDriver->paymentLinks());
}
$url = URL::to("/payment/{$invitation->invitation_key}/token/".$paymentMethod->public_id);
if ($paymentMethod->payment_type_id == PAYMENT_TYPE_ID_PAYPAL) {
$html .= '&nbsp;&nbsp;<span>'.$paymentMethod->email.'</span>';
$url .= '#braintree_paypal';
} elseif ($paymentMethod->payment_type_id != PAYMENT_TYPE_ACH) {
$html .= '<div class="pull-right" style="text-align:right">'.trans('texts.card_expiration', array('expires' => Utils::fromSqlDate($paymentMethod->expiration, false)->format('m/y'))).'<br>';
$html .= '&bull;&bull;&bull;'.$paymentMethod->last4.'</div>';
} else {
$html .= '<div style="text-align:right">';
$html .= '&bull;&bull;&bull;'.$paymentMethod->last4.'</div>';
}
$paymentTypes[] = [
'url' => $url,
'label' => $html,
];
}
}
}
foreach(Gateway::$paymentTypes as $type) {
if ($type == PAYMENT_TYPE_STRIPE) {
continue;
}
if ($gateway = $account->getGatewayByType($type)) {
if ($type == PAYMENT_TYPE_DIRECT_DEBIT) {
if ($gateway->gateway_id == GATEWAY_STRIPE) {
$type = PAYMENT_TYPE_STRIPE_ACH;
} elseif ($gateway->gateway_id == GATEWAY_WEPAY) {
$type = PAYMENT_TYPE_WEPAY_ACH;
}
} elseif ($type == PAYMENT_TYPE_PAYPAL && $gateway->gateway_id == GATEWAY_BRAINTREE) {
$type = PAYMENT_TYPE_BRAINTREE_PAYPAL;
} elseif ($type == PAYMENT_TYPE_CREDIT_CARD && $gateway->gateway_id == GATEWAY_STRIPE) {
$type = PAYMENT_TYPE_STRIPE_CREDIT_CARD;
}
$typeLink = strtolower(str_replace('PAYMENT_TYPE_', '', $type));
$url = URL::to("/payment/{$invitation->invitation_key}/{$typeLink}");
// PayPal doesn't allow being run in an iframe so we need to open in new tab
if ($type === PAYMENT_TYPE_PAYPAL && $account->iframe_url) {
$url = 'javascript:window.open("' . $url . '", "_blank")';
}
if ($type == PAYMENT_TYPE_STRIPE_CREDIT_CARD) {
$label = trans('texts.' . strtolower(PAYMENT_TYPE_CREDIT_CARD));
} elseif ($type == PAYMENT_TYPE_STRIPE_ACH || $type == PAYMENT_TYPE_WEPAY_ACH) {
$label = trans('texts.' . strtolower(PAYMENT_TYPE_DIRECT_DEBIT));
} elseif ($type == PAYMENT_TYPE_BRAINTREE_PAYPAL) {
$label = trans('texts.' . strtolower(PAYMENT_TYPE_PAYPAL));
} else {
$label = trans('texts.' . strtolower($type));
}
$paymentTypes[] = [
'url' => $url, 'label' => $label
];
}
}
return $paymentTypes;
return $links;
}
public function download($invitationKey)
@ -303,6 +210,9 @@ class ClientPortalController extends BaseController
return $this->returnError();
}
$paymentDriver = $account->paymentDriver(false, GATEWAY_TYPE_TOKEN);
$customer = $paymentDriver->customer($client->id);
$data = [
'color' => $color,
'contact' => $contact,
@ -310,15 +220,10 @@ class ClientPortalController extends BaseController
'client' => $client,
'clientFontUrl' => $account->getFontsUrl(),
'gateway' => $account->getTokenGateway(),
'paymentMethods' => $this->paymentService->getClientPaymentMethods($client),
'paymentMethods' => $customer ? $customer->payment_methods : false,
'transactionToken' => $paymentDriver->createTransactionToken(),
];
if ($braintreeGateway = $account->getGatewayConfig(GATEWAY_BRAINTREE)){
if($braintreeGateway->getPayPalEnabled()) {
$data['braintreeClientToken'] = $this->paymentService->getBraintreeClientToken($account);
}
}
return response()->view('invited.dashboard', $data);
}
@ -767,7 +672,9 @@ class ClientPortalController extends BaseController
$client = $contact->client;
$account = $client->account;
$paymentMethods = $this->paymentService->getClientPaymentMethods($client);
$paymentDriver = $account->paymentDriver(false, GATEWAY_TYPE_TOKEN);
$customer = $paymentDriver->customer($client->id);
$data = array(
'account' => $account,
@ -776,17 +683,12 @@ class ClientPortalController extends BaseController
'client' => $client,
'clientViewCSS' => $account->clientViewCSS(),
'clientFontUrl' => $account->getFontsUrl(),
'paymentMethods' => $paymentMethods,
'paymentMethods' => $customer ? $customer->payment_methods : false,
'gateway' => $account->getTokenGateway(),
'title' => trans('texts.payment_methods')
'title' => trans('texts.payment_methods'),
'transactionToken' => $paymentDriver->createTransactionToken(),
);
if ($braintreeGateway = $account->getGatewayConfig(GATEWAY_BRAINTREE)){
if($braintreeGateway->getPayPalEnabled()) {
$data['braintreeClientToken'] = $this->paymentService->getBraintreeClientToken($account);
}
}
return response()->view('payments.paymentmethods', $data);
}
@ -801,7 +703,10 @@ class ClientPortalController extends BaseController
}
$client = $contact->client;
$result = $this->paymentService->verifyClientPaymentMethod($client, $publicId, $amount1, $amount2);
$account = $client->account;
$paymentDriver = $account->paymentDriver(null, GATEWAY_TYPE_BANK_TRANSFER);
$result = $paymentDriver->verifyBankAccount($client, $publicId, $amount1, $amount2);
if (is_string($result)) {
Session::flash('error', $result);
@ -809,7 +714,7 @@ class ClientPortalController extends BaseController
Session::flash('message', trans('texts.payment_method_verified'));
}
return redirect()->to($client->account->enable_client_portal?'/client/dashboard':'/client/paymentmethods/');
return redirect()->to($account->enable_client_portal?'/client/dashboard':'/client/payment_methods/');
}
public function removePaymentMethod($publicId)
@ -819,138 +724,21 @@ class ClientPortalController extends BaseController
}
$client = $contact->client;
$result = $this->paymentService->removeClientPaymentMethod($client, $publicId);
$account = $contact->account;
if (is_string($result)) {
Session::flash('error', $result);
} else {
$paymentDriver = $account->paymentDriver(false, GATEWAY_TYPE_TOKEN);
$paymentMethod = PaymentMethod::clientId($client->id)
->wherePublicId($publicId)
->firstOrFail();
try {
$paymentDriver->removePaymentMethod($paymentMethod);
Session::flash('message', trans('texts.payment_method_removed'));
} catch (Exception $exception) {
Session::flash('error', $exception->getMessage());
}
return redirect()->to($client->account->enable_client_portal?'/client/dashboard':'/client/paymentmethods/');
}
public function addPaymentMethod($paymentType, $token=false)
{
if (!$contact = $this->getContact()) {
return $this->returnError();
}
$client = $contact->client;
$account = $client->account;
$typeLink = $paymentType;
$paymentType = 'PAYMENT_TYPE_' . strtoupper($paymentType);
$accountGateway = $client->account->getTokenGateway();
$gateway = $accountGateway->gateway;
if ($token && $paymentType == PAYMENT_TYPE_BRAINTREE_PAYPAL) {
$sourceReference = $this->paymentService->createToken($paymentType, $this->paymentService->createGateway($accountGateway), array('token'=>$token), $accountGateway, $client, $contact->id);
if(empty($sourceReference)) {
$this->paymentMethodError('Token-No-Ref', $this->paymentService->lastError, $accountGateway);
} else {
Session::flash('message', trans('texts.payment_method_added'));
}
return redirect()->to($account->enable_client_portal?'/client/dashboard':'/client/paymentmethods/');
}
$acceptedCreditCardTypes = $accountGateway->getCreditcardTypes();
$data = [
'showBreadcrumbs' => false,
'client' => $client,
'contact' => $contact,
'gateway' => $gateway,
'accountGateway' => $accountGateway,
'acceptedCreditCardTypes' => $acceptedCreditCardTypes,
'paymentType' => $paymentType,
'countries' => Cache::get('countries'),
'currencyId' => $client->getCurrencyId(),
'currencyCode' => $client->currency ? $client->currency->code : ($account->currency ? $account->currency->code : 'USD'),
'account' => $account,
'url' => URL::to('client/paymentmethods/add/'.$typeLink),
'clientFontUrl' => $account->getFontsUrl(),
'showAddress' => $accountGateway->show_address,
'paymentTitle' => trans('texts.add_payment_method'),
'sourceId' => $token,
];
$details = json_decode(Input::get('details'));
$data['details'] = $details;
if ($paymentType == PAYMENT_TYPE_STRIPE_ACH) {
$data['currencies'] = Cache::get('currencies');
}
if ($gateway->id == GATEWAY_BRAINTREE) {
$data['braintreeClientToken'] = $this->paymentService->getBraintreeClientToken($account);
}
if(!empty($data['braintreeClientToken']) || $accountGateway->getPublishableStripeKey()|| ($accountGateway->gateway_id == GATEWAY_WEPAY && $paymentType != PAYMENT_TYPE_WEPAY_ACH)) {
$data['tokenize'] = true;
}
return View::make('payments.add_paymentmethod', $data);
}
public function postAddPaymentMethod($paymentType)
{
if (!$contact = $this->getContact()) {
return $this->returnError();
}
$client = $contact->client;
$typeLink = $paymentType;
$paymentType = 'PAYMENT_TYPE_' . strtoupper($paymentType);
$account = $client->account;
$accountGateway = $account->getGatewayByType($paymentType);
$sourceToken = Input::get('sourceToken');
if (($validator = PaymentController::processPaymentClientDetails($client, $accountGateway, $paymentType)) !== true) {
return Redirect::to('client/paymentmethods/add/' . $typeLink.'/'.$sourceToken)
->withErrors($validator)
->withInput(Request::except('cvv'));
}
if ($sourceToken) {
$details = array('token' => $sourceToken);
} elseif (Input::get('plaidPublicToken')) {
$usingPlaid = true;
$details = array('plaidPublicToken' => Input::get('plaidPublicToken'), 'plaidAccountId' => Input::get('plaidAccountId'));
}
if (($paymentType == PAYMENT_TYPE_STRIPE_ACH || $paymentType == PAYMENT_TYPE_WEPAY_ACH) && !Input::get('authorize_ach')) {
Session::flash('error', trans('texts.ach_authorization_required'));
return Redirect::to('client/paymentmethods/add/' . $typeLink.'/'.$sourceToken)->withInput(Request::except('cvv'));
}
if ($paymentType == PAYMENT_TYPE_WEPAY_ACH && !Input::get('tos_agree')) {
Session::flash('error', trans('texts.wepay_payment_tos_agree_required'));
return Redirect::to('client/paymentmethods/add/' . $typeLink.'/'.$sourceToken)->withInput(Request::except('cvv'));
}
if (!empty($details)) {
$gateway = $this->paymentService->createGateway($accountGateway);
$sourceReference = $this->paymentService->createToken($paymentType, $gateway, $details, $accountGateway, $client, $contact->id);
} else {
return Redirect::to('client/paymentmethods/add/' . $typeLink.'/'.$sourceToken)->withInput(Request::except('cvv'));
}
if(empty($sourceReference)) {
$this->paymentMethodError('Token-No-Ref', $this->paymentService->lastError, $accountGateway);
return Redirect::to('client/paymentmethods/add/' . $typeLink.'/'.$sourceToken)->withInput(Request::except('cvv'));
} else if ($paymentType == PAYMENT_TYPE_STRIPE_ACH && empty($usingPlaid) ) {
// The user needs to complete verification
Session::flash('message', trans('texts.bank_account_verification_next_steps'));
return Redirect::to($account->enable_client_portal?'/client/dashboard':'/client/paymentmethods/');
} else {
Session::flash('message', trans('texts.payment_method_added'));
return redirect()->to($account->enable_client_portal?'/client/dashboard':'/client/paymentmethods/');
}
return redirect()->to($client->account->enable_client_portal?'/client/dashboard':'/client/payment_methods/');
}
public function setDefaultPaymentMethod(){
@ -959,21 +747,25 @@ class ClientPortalController extends BaseController
}
$client = $contact->client;
$account = $client->account;
$validator = Validator::make(Input::all(), array('source' => 'required'));
if ($validator->fails()) {
return Redirect::to($client->account->enable_client_portal?'/client/dashboard':'/client/paymentmethods/');
return Redirect::to($client->account->enable_client_portal?'/client/dashboard':'/client/payment_methods/');
}
$result = $this->paymentService->setClientDefaultPaymentMethod($client, Input::get('source'));
$paymentDriver = $account->paymentDriver(false, GATEWAY_TYPE_TOKEN);
$paymentMethod = PaymentMethod::clientId($client->id)
->wherePublicId(Input::get('source'))
->firstOrFail();
$customer = $paymentDriver->customer($client->id);
$customer->default_payment_method_id = $paymentMethod->id;
$customer->save();
if (is_string($result)) {
Session::flash('error', $result);
} else {
Session::flash('message', trans('texts.payment_method_set_as_default'));
}
return redirect()->to($client->account->enable_client_portal?'/client/dashboard':'/client/paymentmethods/');
return redirect()->to($client->account->enable_client_portal?'/client/dashboard':'/client/payment_methods/');
}
private function paymentMethodError($type, $error, $accountGateway = false, $exception = false)

View File

@ -200,7 +200,7 @@ class InvoiceController extends BaseController
$data = array_merge($data, self::getViewModel($invoice));
if ($invoice->isSent() && $invoice->getAutoBillEnabled() && !$invoice->isPaid()) {
$data['autoBillChangeWarning'] = $this->paymentService->getClientRequiresDelayedAutoBill($invoice->client);
$data['autoBillChangeWarning'] = $invoice->client->autoBillLater();
}
if ($clone) {

View File

@ -0,0 +1,236 @@
<?php namespace App\Http\Controllers;
use CreditCard;
use Session;
use Input;
use Utils;
use View;
use Validator;
use URL;
use Cache;
use Omnipay;
use App\Models\Country;
use App\Models\License;
use App\Models\Affiliate;
use App\Ninja\Repositories\AccountRepository;
use App\Ninja\Mailers\ContactMailer;
class NinjaController extends BaseController
{
public function __construct(AccountRepository $accountRepo, ContactMailer $contactMailer)
{
$this->accountRepo = $accountRepo;
$this->contactMailer = $contactMailer;
}
private function getLicensePaymentDetails($input, $affiliate)
{
$country = Country::find($input['country_id']);
$data = [
'firstName' => $input['first_name'],
'lastName' => $input['last_name'],
'email' => $input['email'],
'number' => $input['card_number'],
'expiryMonth' => $input['expiration_month'],
'expiryYear' => $input['expiration_year'],
'cvv' => $input['cvv'],
'billingAddress1' => $input['address1'],
'billingAddress2' => $input['address2'],
'billingCity' => $input['city'],
'billingState' => $input['state'],
'billingPostcode' => $input['postal_code'],
'billingCountry' => $country->iso_3166_2,
'shippingAddress1' => $input['address1'],
'shippingAddress2' => $input['address2'],
'shippingCity' => $input['city'],
'shippingState' => $input['state'],
'shippingPostcode' => $input['postal_code'],
'shippingCountry' => $country->iso_3166_2
];
$card = new CreditCard($data);
return [
'amount' => $affiliate->price,
'card' => $card,
'currency' => 'USD',
'returnUrl' => URL::to('license_complete'),
'cancelUrl' => URL::to('/')
];
}
public function show_license_payment()
{
if (Input::has('return_url')) {
Session::set('return_url', Input::get('return_url'));
}
if (Input::has('affiliate_key')) {
if ($affiliate = Affiliate::where('affiliate_key', '=', Input::get('affiliate_key'))->first()) {
Session::set('affiliate_id', $affiliate->id);
}
}
if (Input::has('product_id')) {
Session::set('product_id', Input::get('product_id'));
} else if (!Session::has('product_id')) {
Session::set('product_id', PRODUCT_ONE_CLICK_INSTALL);
}
if (!Session::get('affiliate_id')) {
return Utils::fatalError();
}
if (Utils::isNinjaDev() && Input::has('test_mode')) {
Session::set('test_mode', Input::get('test_mode'));
}
$account = $this->accountRepo->getNinjaAccount();
$account->load('account_gateways.gateway');
$accountGateway = $account->getGatewayByType(GATEWAY_TYPE_CREDIT_CARD);
$gateway = $accountGateway->gateway;
$acceptedCreditCardTypes = $accountGateway->getCreditcardTypes();
$affiliate = Affiliate::find(Session::get('affiliate_id'));
$data = [
'showBreadcrumbs' => false,
'hideHeader' => true,
'url' => 'license',
'amount' => $affiliate->price,
'client' => false,
'contact' => false,
'gateway' => $gateway,
'account' => $account,
'accountGateway' => $accountGateway,
'acceptedCreditCardTypes' => $acceptedCreditCardTypes,
'countries' => Cache::get('countries'),
'currencyId' => 1,
'currencyCode' => 'USD',
'paymentTitle' => $affiliate->payment_title,
'paymentSubtitle' => $affiliate->payment_subtitle,
'showAddress' => true,
];
return View::make('payments.stripe.credit_card', $data);
}
public function do_license_payment()
{
$testMode = Session::get('test_mode') === 'true';
$rules = array(
'first_name' => 'required',
'last_name' => 'required',
'email' => 'required',
'card_number' => 'required',
'expiration_month' => 'required',
'expiration_year' => 'required',
'cvv' => 'required',
'address1' => 'required',
'city' => 'required',
'state' => 'required',
'postal_code' => 'required',
'country_id' => 'required',
);
$validator = Validator::make(Input::all(), $rules);
if ($validator->fails()) {
return redirect()->to('license')
->withErrors($validator)
->withInput();
}
$account = $this->accountRepo->getNinjaAccount();
$account->load('account_gateways.gateway');
$accountGateway = $account->getGatewayByType(GATEWAY_TYPE_CREDIT_CARD);
try {
$affiliate = Affiliate::find(Session::get('affiliate_id'));
if ($testMode) {
$ref = 'TEST_MODE';
} else {
$details = self::getLicensePaymentDetails(Input::all(), $affiliate);
$gateway = Omnipay::create($accountGateway->gateway->provider);
$gateway->initialize((array) $accountGateway->getConfig());
$response = $gateway->purchase($details)->send();
$ref = $response->getTransactionReference();
if (!$response->isSuccessful() || !$ref) {
$this->error('License', $response->getMessage(), $accountGateway);
return redirect()->to('license')->withInput();
}
}
$licenseKey = Utils::generateLicense();
$license = new License();
$license->first_name = Input::get('first_name');
$license->last_name = Input::get('last_name');
$license->email = Input::get('email');
$license->transaction_reference = $ref;
$license->license_key = $licenseKey;
$license->affiliate_id = Session::get('affiliate_id');
$license->product_id = Session::get('product_id');
$license->save();
$data = [
'message' => $affiliate->payment_subtitle,
'license' => $licenseKey,
'hideHeader' => true,
'productId' => $license->product_id,
'price' => $affiliate->price,
];
$name = "{$license->first_name} {$license->last_name}";
$this->contactMailer->sendLicensePaymentConfirmation($name, $license->email, $affiliate->price, $license->license_key, $license->product_id);
if (Session::has('return_url')) {
$data['redirectTo'] = Session::get('return_url')."?license_key={$license->license_key}&product_id=".Session::get('product_id');
$data['message'] = "Redirecting to " . Session::get('return_url');
}
return View::make('public.license', $data);
} catch (\Exception $e) {
$this->error('License-Uncaught', false, $accountGateway, $e);
return redirect()->to('license')->withInput();
}
}
public function claim_license()
{
$licenseKey = Input::get('license_key');
$productId = Input::get('product_id', PRODUCT_ONE_CLICK_INSTALL);
$license = License::where('license_key', '=', $licenseKey)
->where('is_claimed', '<', 10)
->where('product_id', '=', $productId)
->first();
if ($license) {
if ($license->transaction_reference != 'TEST_MODE') {
$license->is_claimed = $license->is_claimed + 1;
$license->save();
}
if ($productId == PRODUCT_INVOICE_DESIGNS) {
return file_get_contents(storage_path() . '/invoice_designs.txt');
} else {
// temporary fix to enable previous version to work
if (Input::get('get_date')) {
return $license->created_at->format('Y-m-d');
} else {
return 'valid';
}
}
} else {
return RESULT_FAILURE;
}
}
}

View File

@ -0,0 +1,333 @@
<?php namespace App\Http\Controllers;
use Session;
use Input;
use Request;
use Utils;
use View;
use Validator;
use Cache;
use Exception;
use App\Models\Invitation;
use App\Models\Account;
use App\Models\Payment;
use App\Models\PaymentMethod;
use App\Services\PaymentService;
use App\Ninja\Mailers\UserMailer;
use App\Http\Requests\CreateOnlinePaymentRequest;
class OnlinePaymentController extends BaseController
{
public function __construct(PaymentService $paymentService, UserMailer $userMailer)
{
$this->paymentService = $paymentService;
$this->userMailer = $userMailer;
}
public function showPayment($invitationKey, $gatewayType = false, $sourceId = false)
{
$invitation = Invitation::with('invoice.invoice_items', 'invoice.client.currency', 'invoice.client.account.account_gateways.gateway')
->where('invitation_key', '=', $invitationKey)->firstOrFail();
if ( ! $gatewayType) {
$gatewayType = Session::get($invitation->id . 'gateway_type');
}
$paymentDriver = $invitation->account->paymentDriver($invitation, $gatewayType);
try {
return $paymentDriver->startPurchase(Input::all(), $sourceId);
} catch (Exception $exception) {
return $this->error($paymentDriver, $exception);
}
}
public function doPayment(CreateOnlinePaymentRequest $request)
{
$invitation = $request->invitation;
$gatewayType = Session::get($invitation->id . 'gateway_type');
$paymentDriver = $invitation->account->paymentDriver($invitation, $gatewayType);
try {
$paymentDriver->completeOnsitePurchase($request->all());
if ($paymentDriver->isTwoStep()) {
Session::flash('warning', trans('texts.bank_account_verification_next_steps'));
} else {
Session::flash('message', trans('texts.applied_payment'));
}
return redirect()->to('view/' . $invitation->invitation_key);
} catch (Exception $exception) {
return $this->error($paymentDriver, $exception);
}
}
public function offsitePayment($invitationKey = false, $gatewayType = false)
{
$invitationKey = $invitationKey ?: Session::get('invitation_key');
$invitation = Invitation::with('invoice.invoice_items', 'invoice.client.currency', 'invoice.client.account.account_gateways.gateway')
->where('invitation_key', '=', $invitationKey)->firstOrFail();
$gatewayType = $gatewayType ?: Session::get($invitation->id . 'gateway_type');
$paymentDriver = $invitation->account->paymentDriver($invitation, $gatewayType);
if ($error = Input::get('error_description') ?: Input::get('error')) {
return $this->error($paymentDriver, $error);
}
try {
$paymentDriver->completeOffsitePurchase(Input::all());
Session::flash('message', trans('texts.applied_payment'));
return redirect()->to('view/' . $invitation->invitation_key);
} catch (Exception $exception) {
return $this->error($paymentDriver, $exception);
}
}
private function error($paymentDriver, $exception)
{
if (is_string($exception)) {
$displayError = $exception;
$logError = $exception;
} else {
$displayError = $exception->getMessage();
$logError = Utils::getErrorString($exception);
}
$message = sprintf('%s: %s', ucwords($paymentDriver->providerName()), $displayError);
Session::flash('error', $message);
$message = sprintf('Payment Error [%s]: %s', $paymentDriver->providerName(), $logError);
Utils::logError($message, 'PHP', true);
return redirect()->to('view/' . $paymentDriver->invitation->invitation_key);
}
public function getBankInfo($routingNumber) {
if (strlen($routingNumber) != 9 || !preg_match('/\d{9}/', $routingNumber)) {
return response()->json([
'message' => 'Invalid routing number',
], 400);
}
$data = PaymentMethod::lookupBankData($routingNumber);
if (is_string($data)) {
return response()->json([
'message' => $data,
], 500);
} elseif (!empty($data)) {
return response()->json($data);
}
return response()->json([
'message' => 'Bank not found',
], 404);
}
public function handlePaymentWebhook($accountKey, $gatewayId)
{
$gatewayId = intval($gatewayId);
$account = Account::where('accounts.account_key', '=', $accountKey)->first();
if (!$account) {
return response()->json([
'message' => 'Unknown account',
], 404);
}
$accountGateway = $account->getGatewayConfig(intval($gatewayId));
if (!$accountGateway) {
return response()->json([
'message' => 'Unknown gateway',
], 404);
}
switch($gatewayId) {
case GATEWAY_STRIPE:
return $this->handleStripeWebhook($accountGateway);
case GATEWAY_WEPAY:
return $this->handleWePayWebhook($accountGateway);
default:
return response()->json([
'message' => 'Unsupported gateway',
], 404);
}
}
protected function handleWePayWebhook($accountGateway) {
$data = Input::all();
$accountId = $accountGateway->account_id;
foreach (array_keys($data) as $key) {
if ('_id' == substr($key, -3)) {
$objectType = substr($key, 0, -3);
$objectId = $data[$key];
break;
}
}
if (!isset($objectType)) {
return response()->json([
'message' => 'Could not find object id parameter',
], 400);
}
if ($objectType == 'credit_card') {
$paymentMethod = PaymentMethod::scope(false, $accountId)->where('source_reference', '=', $objectId)->first();
if (!$paymentMethod) {
return array('message' => 'Unknown payment method');
}
$wepay = \Utils::setupWePay($accountGateway);
$source = $wepay->request('credit_card', array(
'client_id' => WEPAY_CLIENT_ID,
'client_secret' => WEPAY_CLIENT_SECRET,
'credit_card_id' => intval($objectId),
));
if ($source->state == 'deleted') {
$paymentMethod->delete();
} else {
$this->paymentService->convertPaymentMethodFromWePay($source, null, $paymentMethod)->save();
}
return array('message' => 'Processed successfully');
} elseif ($objectType == 'account') {
$config = $accountGateway->getConfig();
if ($config->accountId != $objectId) {
return array('message' => 'Unknown account');
}
$wepay = \Utils::setupWePay($accountGateway);
$wepayAccount = $wepay->request('account', array(
'account_id' => intval($objectId),
));
if ($wepayAccount->state == 'deleted') {
$accountGateway->delete();
} else {
$config->state = $wepayAccount->state;
$accountGateway->setConfig($config);
$accountGateway->save();
}
return array('message' => 'Processed successfully');
} elseif ($objectType == 'checkout') {
$payment = Payment::scope(false, $accountId)->where('transaction_reference', '=', $objectId)->first();
if (!$payment) {
return array('message' => 'Unknown payment');
}
$wepay = \Utils::setupWePay($accountGateway);
$checkout = $wepay->request('checkout', array(
'checkout_id' => intval($objectId),
));
if ($checkout->state == 'refunded') {
$payment->recordRefund();
} elseif (!empty($checkout->refund) && !empty($checkout->refund->amount_refunded) && ($checkout->refund->amount_refunded - $payment->refunded) > 0) {
$payment->recordRefund($checkout->refund->amount_refunded - $payment->refunded);
}
if ($checkout->state == 'captured') {
$payment->markComplete();
} elseif ($checkout->state == 'cancelled') {
$payment->markCancelled();
} elseif ($checkout->state == 'failed') {
$payment->markFailed();
}
return array('message' => 'Processed successfully');
} else {
return array('message' => 'Ignoring event');
}
}
protected function handleStripeWebhook($accountGateway) {
$eventId = Input::get('id');
$eventType= Input::get('type');
$accountId = $accountGateway->account_id;
if (!$eventId) {
return response()->json(['message' => 'Missing event id'], 400);
}
if (!$eventType) {
return response()->json(['message' => 'Missing event type'], 400);
}
$supportedEvents = array(
'charge.failed',
'charge.succeeded',
'customer.source.updated',
'customer.source.deleted',
);
if (!in_array($eventType, $supportedEvents)) {
return array('message' => 'Ignoring event');
}
// Fetch the event directly from Stripe for security
$eventDetails = $this->paymentService->makeStripeCall($accountGateway, 'GET', 'events/'.$eventId);
if (is_string($eventDetails) || !$eventDetails) {
return response()->json([
'message' => $eventDetails ? $eventDetails : 'Could not get event details.',
], 500);
}
if ($eventType != $eventDetails['type']) {
return response()->json(['message' => 'Event type mismatch'], 400);
}
if (!$eventDetails['pending_webhooks']) {
return response()->json(['message' => 'This is not a pending event'], 400);
}
if ($eventType == 'charge.failed' || $eventType == 'charge.succeeded') {
$charge = $eventDetails['data']['object'];
$transactionRef = $charge['id'];
$payment = Payment::scope(false, $accountId)->where('transaction_reference', '=', $transactionRef)->first();
if (!$payment) {
return array('message' => 'Unknown payment');
}
if ($eventType == 'charge.failed') {
if (!$payment->isFailed()) {
$payment->markFailed($charge['failure_message']);
$this->userMailer->sendNotification($payment->user, $payment->invoice, 'payment_failed', $payment);
}
} elseif ($eventType == 'charge.succeeded') {
$payment->markComplete();
} elseif ($eventType == 'charge.refunded') {
$payment->recordRefund($charge['amount_refunded'] / 100 - $payment->refunded);
}
} elseif($eventType == 'customer.source.updated' || $eventType == 'customer.source.deleted') {
$source = $eventDetails['data']['object'];
$sourceRef = $source['id'];
$paymentMethod = PaymentMethod::scope(false, $accountId)->where('source_reference', '=', $sourceRef)->first();
if (!$paymentMethod) {
return array('message' => 'Unknown payment method');
}
if ($eventType == 'customer.source.deleted') {
$paymentMethod->delete();
} elseif ($eventType == 'customer.source.updated') {
$this->paymentService->convertPaymentMethodFromStripe($source, null, $paymentMethod)->save();
}
}
return array('message' => 'Processed successfully');
}
}

View File

@ -1,33 +1,16 @@
<?php namespace App\Http\Controllers;
use Datatable;
use Input;
use Redirect;
use Request;
use Session;
use Utils;
use View;
use Validator;
use Omnipay;
use CreditCard;
use URL;
use Cache;
use App\Models\Invoice;
use App\Models\Invitation;
use App\Models\Client;
use App\Models\Account;
use App\Models\PaymentType;
use App\Models\License;
use App\Models\Payment;
use App\Models\Affiliate;
use App\Models\PaymentMethod;
use App\Ninja\Repositories\PaymentRepository;
use App\Ninja\Repositories\InvoiceRepository;
use App\Ninja\Repositories\AccountRepository;
use App\Ninja\Mailers\ContactMailer;
use App\Ninja\Mailers\UserMailer;
use App\Services\PaymentService;
use App\Http\Requests\PaymentRequest;
use App\Http\Requests\CreatePaymentRequest;
use App\Http\Requests\UpdatePaymentRequest;
@ -36,16 +19,11 @@ class PaymentController extends BaseController
{
protected $entityType = ENTITY_PAYMENT;
public function __construct(PaymentRepository $paymentRepo, InvoiceRepository $invoiceRepo, AccountRepository $accountRepo, ContactMailer $contactMailer, PaymentService $paymentService, UserMailer $userMailer)
public function __construct(PaymentRepository $paymentRepo, ContactMailer $contactMailer, PaymentService $paymentService)
{
// parent::__construct();
$this->paymentRepo = $paymentRepo;
$this->invoiceRepo = $invoiceRepo;
$this->accountRepo = $accountRepo;
$this->contactMailer = $contactMailer;
$this->paymentService = $paymentService;
$this->userMailer = $userMailer;
}
public function index()
@ -121,606 +99,6 @@ class PaymentController extends BaseController
return View::make('payments.edit', $data);
}
private function getLicensePaymentDetails($input, $affiliate)
{
$data = $this->paymentService->convertInputForOmnipay($input);
$card = new CreditCard($data);
return [
'amount' => $affiliate->price,
'card' => $card,
'currency' => 'USD',
'returnUrl' => URL::to('license_complete'),
'cancelUrl' => URL::to('/')
];
}
public function show_payment($invitationKey, $paymentType = false, $sourceId = false)
{
$invitation = Invitation::with('invoice.invoice_items', 'invoice.client.currency', 'invoice.client.account.account_gateways.gateway')->where('invitation_key', '=', $invitationKey)->firstOrFail();
$invoice = $invitation->invoice;
$client = $invoice->client;
$account = $client->account;
$useToken = false;
if ($paymentType) {
$paymentType = 'PAYMENT_TYPE_' . strtoupper($paymentType);
} else {
$paymentType = Session::get($invitation->id . 'payment_type') ?:
$account->account_gateways[0]->getPaymentType();
}
$data = array();
Session::put($invitation->id.'payment_ref', $invoice->id.'_'.uniqid());
$details = json_decode(Input::get('details'));
$data['details'] = $details;
if ($paymentType == PAYMENT_TYPE_BRAINTREE_PAYPAL) {
if ($deviceData = Input::get('device_data')) {
Session::put($invitation->id . 'device_data', $deviceData);
}
Session::put($invitation->id . 'payment_type', PAYMENT_TYPE_BRAINTREE_PAYPAL);
if (!$sourceId || !$details) {
return Redirect::to('view/'.$invitationKey);
}
} elseif ($paymentType == PAYMENT_TYPE_WEPAY_ACH) {
Session::put($invitation->id . 'payment_type', PAYMENT_TYPE_WEPAY_ACH);
if (!$sourceId) {
return Redirect::to('view/'.$invitationKey);
}
} else {
if ($paymentType == PAYMENT_TYPE_TOKEN) {
$useToken = true;
$accountGateway = $invoice->client->account->getTokenGateway();
$paymentType = $accountGateway->getPaymentType();
} else {
$accountGateway = $invoice->client->account->getGatewayByType($paymentType);
}
Session::put($invitation->id . 'payment_type', $paymentType);
$gateway = $accountGateway->gateway;
$acceptedCreditCardTypes = $accountGateway->getCreditcardTypes();
$isOffsite = ($paymentType != PAYMENT_TYPE_CREDIT_CARD && $accountGateway->getPaymentType() != PAYMENT_TYPE_STRIPE)
|| $gateway->id == GATEWAY_EWAY
|| $gateway->id == GATEWAY_TWO_CHECKOUT
|| $gateway->id == GATEWAY_PAYFAST
|| $gateway->id == GATEWAY_MOLLIE;
// Handle offsite payments
if ($useToken || $isOffsite) {
if (Session::has('error')) {
Session::reflash();
return Redirect::to('view/' . $invitationKey);
} else {
return self::do_payment($invitationKey, false, $useToken, $sourceId);
}
}
$data += [
'accountGateway' => $accountGateway,
'acceptedCreditCardTypes' => $acceptedCreditCardTypes,
'gateway' => $gateway,
'showAddress' => $accountGateway->show_address,
];
if ($paymentType == PAYMENT_TYPE_STRIPE_ACH) {
$data['currencies'] = Cache::get('currencies');
}
if ($gateway->id == GATEWAY_BRAINTREE) {
$data['braintreeClientToken'] = $this->paymentService->getBraintreeClientToken($account);
}
if(!empty($data['braintreeClientToken']) || $accountGateway->getPublishableStripeKey()|| $accountGateway->gateway_id == GATEWAY_WEPAY) {
$data['tokenize'] = true;
}
}
$data += [
'showBreadcrumbs' => false,
'url' => 'payment/'.$invitationKey,
'amount' => $invoice->getRequestedAmount(),
'invoiceNumber' => $invoice->invoice_number,
'client' => $client,
'contact' => $invitation->contact,
'paymentType' => $paymentType,
'countries' => Cache::get('countries'),
'currencyId' => $client->getCurrencyId(),
'currencyCode' => $client->currency ? $client->currency->code : ($account->currency ? $account->currency->code : 'USD'),
'account' => $client->account,
'sourceId' => $sourceId,
'clientFontUrl' => $client->account->getFontsUrl(),
];
return View::make('payments.add_paymentmethod', $data);
}
public function show_license_payment()
{
if (Input::has('return_url')) {
Session::set('return_url', Input::get('return_url'));
}
if (Input::has('affiliate_key')) {
if ($affiliate = Affiliate::where('affiliate_key', '=', Input::get('affiliate_key'))->first()) {
Session::set('affiliate_id', $affiliate->id);
}
}
if (Input::has('product_id')) {
Session::set('product_id', Input::get('product_id'));
} else if (!Session::has('product_id')) {
Session::set('product_id', PRODUCT_ONE_CLICK_INSTALL);
}
if (!Session::get('affiliate_id')) {
return Utils::fatalError();
}
if (Utils::isNinjaDev() && Input::has('test_mode')) {
Session::set('test_mode', Input::get('test_mode'));
}
$account = $this->accountRepo->getNinjaAccount();
$account->load('account_gateways.gateway');
$accountGateway = $account->getGatewayByType(PAYMENT_TYPE_STRIPE_CREDIT_CARD);
$gateway = $accountGateway->gateway;
$acceptedCreditCardTypes = $accountGateway->getCreditcardTypes();
$affiliate = Affiliate::find(Session::get('affiliate_id'));
$data = [
'showBreadcrumbs' => false,
'hideHeader' => true,
'url' => 'license',
'amount' => $affiliate->price,
'client' => false,
'contact' => false,
'gateway' => $gateway,
'account' => $account,
'accountGateway' => $accountGateway,
'acceptedCreditCardTypes' => $acceptedCreditCardTypes,
'countries' => Cache::get('countries'),
'currencyId' => 1,
'currencyCode' => 'USD',
'paymentTitle' => $affiliate->payment_title,
'paymentSubtitle' => $affiliate->payment_subtitle,
'showAddress' => true,
];
return View::make('payments.add_paymentmethod', $data);
}
public function do_license_payment()
{
$testMode = Session::get('test_mode') === 'true';
$rules = array(
'first_name' => 'required',
'last_name' => 'required',
'card_number' => 'required',
'expiration_month' => 'required',
'expiration_year' => 'required',
'cvv' => 'required',
'address1' => 'required',
'city' => 'required',
'state' => 'required',
'postal_code' => 'required',
'country_id' => 'required',
);
$validator = Validator::make(Input::all(), $rules);
if ($validator->fails()) {
return Redirect::to('license')
->withErrors($validator)
->withInput();
}
$account = $this->accountRepo->getNinjaAccount();
$account->load('account_gateways.gateway');
$accountGateway = $account->getGatewayByType(PAYMENT_TYPE_STRIPE_CREDIT_CARD);
try {
$affiliate = Affiliate::find(Session::get('affiliate_id'));
if ($testMode) {
$ref = 'TEST_MODE';
} else {
$details = self::getLicensePaymentDetails(Input::all(), $affiliate);
$response = $this->paymentService->purchase($accountGateway, $details);
$ref = $response->getTransactionReference();
if (!$response->isSuccessful() || !$ref) {
$this->error('License', $response->getMessage(), $accountGateway);
return Redirect::to('license')->withInput();
}
}
$licenseKey = Utils::generateLicense();
$license = new License();
$license->first_name = Input::get('first_name');
$license->last_name = Input::get('last_name');
$license->email = Input::get('email');
$license->transaction_reference = $ref;
$license->license_key = $licenseKey;
$license->affiliate_id = Session::get('affiliate_id');
$license->product_id = Session::get('product_id');
$license->save();
$data = [
'message' => $affiliate->payment_subtitle,
'license' => $licenseKey,
'hideHeader' => true,
'productId' => $license->product_id,
'price' => $affiliate->price,
];
$name = "{$license->first_name} {$license->last_name}";
$this->contactMailer->sendLicensePaymentConfirmation($name, $license->email, $affiliate->price, $license->license_key, $license->product_id);
if (Session::has('return_url')) {
$data['redirectTo'] = Session::get('return_url')."?license_key={$license->license_key}&product_id=".Session::get('product_id');
$data['message'] = "Redirecting to " . Session::get('return_url');
}
return View::make('public.license', $data);
} catch (\Exception $e) {
$this->error('License-Uncaught', false, $accountGateway, $e);
return Redirect::to('license')->withInput();
}
}
public function claim_license()
{
$licenseKey = Input::get('license_key');
$productId = Input::get('product_id', PRODUCT_ONE_CLICK_INSTALL);
$license = License::where('license_key', '=', $licenseKey)
->where('is_claimed', '<', 5)
->where('product_id', '=', $productId)
->first();
if ($license) {
if ($license->transaction_reference != 'TEST_MODE') {
$license->is_claimed = $license->is_claimed + 1;
$license->save();
}
if ($productId == PRODUCT_INVOICE_DESIGNS) {
return file_get_contents(storage_path() . '/invoice_designs.txt');
} else {
// temporary fix to enable previous version to work
if (Input::get('get_date')) {
return $license->created_at->format('Y-m-d');
} else {
return 'valid';
}
}
} else {
return RESULT_FAILURE;
}
}
public static function processPaymentClientDetails($client, $accountGateway, $paymentType, $onSite = true){
$rules = ($paymentType == PAYMENT_TYPE_STRIPE_ACH || $paymentType == PAYMENT_TYPE_WEPAY_ACH)? [] : [
'first_name' => 'required',
'last_name' => 'required',
];
if ( !Input::get('sourceToken') && !(Input::get('plaidPublicToken') && Input::get('plaidAccountId'))) {
$rules = array_merge(
$rules,
[
'card_number' => 'required',
'expiration_month' => 'required',
'expiration_year' => 'required',
'cvv' => 'required',
]
);
}
$requireAddress = $accountGateway->show_address && $paymentType != PAYMENT_TYPE_STRIPE_ACH && $paymentType != PAYMENT_TYPE_BRAINTREE_PAYPAL && $paymentType != PAYMENT_TYPE_WEPAY_ACH;
if ($requireAddress) {
$rules = array_merge($rules, [
'address1' => 'required',
'city' => 'required',
'state' => 'required',
'postal_code' => 'required',
'country_id' => 'required',
]);
}
if ($onSite) {
$validator = Validator::make(Input::all(), $rules);
if ($validator->fails()) {
return $validator;
}
if ($requireAddress && $accountGateway->update_address) {
$client->address1 = trim(Input::get('address1'));
$client->address2 = trim(Input::get('address2'));
$client->city = trim(Input::get('city'));
$client->state = trim(Input::get('state'));
$client->postal_code = trim(Input::get('postal_code'));
$client->country_id = Input::get('country_id');
$client->save();
}
}
return true;
}
public function do_payment($invitationKey, $onSite = true, $useToken = false, $sourceId = false)
{
$invitation = Invitation::with('invoice.invoice_items', 'invoice.client.currency', 'invoice.client.account.currency', 'invoice.client.account.account_gateways.gateway')->where('invitation_key', '=', $invitationKey)->firstOrFail();
$invoice = $invitation->invoice;
$client = $invoice->client;
$account = $client->account;
$paymentType = Session::get($invitation->id . 'payment_type');
$accountGateway = $account->getGatewayByType($paymentType);
$paymentMethod = null;
if ($useToken) {
if(!$sourceId) {
Session::flash('error', trans('texts.no_payment_method_specified'));
return Redirect::to('payment/' . $invitationKey)->withInput(Request::except('cvv'));
} else {
$customerReference = $client->getGatewayToken($accountGateway, $accountGatewayToken/* return parameter*/);
$paymentMethod = PaymentMethod::scope($sourceId, $account->id, $accountGatewayToken->id)->firstOrFail();
$sourceReference = $paymentMethod->source_reference;
// What type of payment is this?
if ($paymentMethod->payment_type_id == PAYMENT_TYPE_ACH) {
if ($accountGateway->gateway_id == GATEWAY_STRIPE) {
$paymentType = PAYMENT_TYPE_STRIPE_ACH;
} elseif ($accountGateway->gateway_id == GATEWAY_WEPAY) {
$paymentType = PAYMENT_TYPE_WEPAY_ACH;
}
} elseif ($paymentMethod->payment_type_id == PAYMENT_TYPE_ID_PAYPAL && $accountGateway->gateway_id == GATEWAY_BRAINTREE) {
$paymentType = PAYMENT_TYPE_BRAINTREE_PAYPAL;
} elseif ($accountGateway->gateway_id == GATEWAY_STRIPE) {
$paymentType = PAYMENT_TYPE_STRIPE_CREDIT_CARD;
} else {
$paymentType = PAYMENT_TYPE_CREDIT_CARD;
}
}
}
if (($validator = static::processPaymentClientDetails($client, $accountGateway, $paymentType, $onSite)) !== true) {
return Redirect::to('payment/'.$invitationKey)
->withErrors($validator)
->withInput(Request::except('cvv'));
}
try {
// For offsite payments send the client's details on file
// If we're using a token then we don't need to send any other data
if (!$onSite || $useToken) {
$data = false;
} else {
$data = Input::all();
}
$gateway = $this->paymentService->createGateway($accountGateway);
$details = $this->paymentService->getPaymentDetails($invitation, $accountGateway, $data);
$details['paymentType'] = $paymentType;
// Check for authorization
if (($paymentType == PAYMENT_TYPE_STRIPE_ACH || $paymentType == PAYMENT_TYPE_WEPAY_ACH) && !Input::get('authorize_ach')) {
Session::flash('error', trans('texts.ach_authorization_required'));
return Redirect::to('client/paymentmethods/add/' . $typeLink.'/'.$sourceToken)->withInput(Request::except('cvv'));
}
if ($paymentType == PAYMENT_TYPE_WEPAY_ACH && !Input::get('tos_agree')) {
Session::flash('error', trans('texts.wepay_payment_tos_agree_required'));
return Redirect::to('client/paymentmethods/add/' . $typeLink.'/'.$sourceToken)->withInput(Request::except('cvv'));
}
// check if we're creating/using a billing token
$tokenBillingSupported = false;
$sourceReferenceParam = 'token';
if ($accountGateway->gateway_id == GATEWAY_STRIPE) {
$tokenBillingSupported = true;
$customerReferenceParam = 'customerReference';
} elseif ($accountGateway->gateway_id == GATEWAY_BRAINTREE) {
$tokenBillingSupported = true;
$sourceReferenceParam = 'paymentMethodToken';
$customerReferenceParam = 'customerId';
$deviceData = Input::get('device_data');
if (!$deviceData) {
$deviceData = Session::get($invitation->id . 'device_data');
}
if($deviceData) {
$details['device_data'] = $deviceData;
}
} elseif ($accountGateway->gateway_id == GATEWAY_WEPAY) {
$tokenBillingSupported = true;
$customerReferenceParam = false;
}
if ($tokenBillingSupported) {
if ($useToken) {
if ($customerReferenceParam) {
$details[$customerReferenceParam] = $customerReference;
}
$details[$sourceReferenceParam] = $sourceReference;
unset($details['card']);
} elseif ($account->token_billing_type_id == TOKEN_BILLING_ALWAYS || Input::get('token_billing') || $paymentType == PAYMENT_TYPE_STRIPE_ACH || $paymentType == PAYMENT_TYPE_WEPAY_ACH) {
$token = $this->paymentService->createToken($paymentType, $gateway, $details, $accountGateway, $client, $invitation->contact_id, $customerReference/* return parameter */, $paymentMethod/* return parameter */);
if ($token) {
$details[$sourceReferenceParam] = $token;
if ($customerReferenceParam) {
$details[$customerReferenceParam] = $customerReference;
}
if ($paymentType == PAYMENT_TYPE_STRIPE_ACH && empty(Input::get('plaidPublicToken')) ) {
// The user needs to complete verification
Session::flash('message', trans('texts.bank_account_verification_next_steps'));
return Redirect::to('/client/paymentmethods');
}
} else {
$this->error('Token-No-Ref', $this->paymentService->lastError, $accountGateway);
return Redirect::to('payment/'.$invitationKey)->withInput(Request::except('cvv'));
}
}
}
$response = $this->paymentService->purchase($accountGateway, $details);
if ($accountGateway->gateway_id == GATEWAY_EWAY) {
$ref = $response->getData()['AccessCode'];
} elseif ($accountGateway->gateway_id == GATEWAY_TWO_CHECKOUT) {
$ref = $response->getData()['cart_order_id'];
} elseif ($accountGateway->gateway_id == GATEWAY_PAYFAST) {
$ref = $response->getData()['m_payment_id'];
} elseif ($accountGateway->gateway_id == GATEWAY_GOCARDLESS) {
$ref = $response->getData()['signature'];
} elseif ($accountGateway->gateway_id == GATEWAY_CYBERSOURCE) {
$ref = $response->getData()['transaction_uuid'];
} else {
$ref = $response->getTransactionReference();
}
if (!$ref) {
$this->error('No-Ref', $response->getMessage(), $accountGateway);
if ($onSite && $paymentType != PAYMENT_TYPE_BRAINTREE_PAYPAL && $paymentType != PAYMENT_TYPE_WEPAY_ACH) {
return Redirect::to('payment/'.$invitationKey)
->withInput(Request::except('cvv'));
} else {
return Redirect::to('view/'.$invitationKey);
}
}
if ($response->isSuccessful()) {
$payment = $this->paymentService->createPayment($invitation, $accountGateway, $ref, null, $details, $paymentMethod, $response);
Session::flash('message', trans('texts.applied_payment'));
if ($account->account_key == NINJA_ACCOUNT_KEY) {
Session::flash('trackEventCategory', '/account');
Session::flash('trackEventAction', '/buy_pro_plan');
Session::flash('trackEventAmount', $payment->amount);
}
return Redirect::to('view/'.$payment->invitation->invitation_key);
} elseif ($response->isRedirect()) {
$invitation->transaction_reference = $ref;
$invitation->save();
Session::put('transaction_reference', $ref);
Session::save();
$response->redirect();
} else {
$this->error('Unknown', $response->getMessage(), $accountGateway);
if ($onSite && $paymentType != PAYMENT_TYPE_BRAINTREE_PAYPAL && $paymentType != PAYMENT_TYPE_WEPAY_ACH) {
return Redirect::to('payment/'.$invitationKey)->withInput(Request::except('cvv'));
} else {
return Redirect::to('view/'.$invitationKey);
}
}
} catch (\Exception $e) {
$this->error('Uncaught', false, $accountGateway, $e);
if ($onSite && $paymentType != PAYMENT_TYPE_BRAINTREE_PAYPAL && $paymentType != PAYMENT_TYPE_WEPAY_ACH) {
return Redirect::to('payment/'.$invitationKey)->withInput(Request::except('cvv'));
} else {
return Redirect::to('view/'.$invitationKey);
}
}
}
public function offsite_payment()
{
$payerId = Request::query('PayerID');
$token = Request::query('token');
if (!$token) {
$token = Session::pull('transaction_reference');
}
if (!$token) {
return redirect(NINJA_WEB_URL);
}
$invitation = Invitation::with('invoice.client.currency', 'invoice.client.account.account_gateways.gateway')->where('transaction_reference', '=', $token)->firstOrFail();
$invoice = $invitation->invoice;
$client = $invoice->client;
$account = $client->account;
if ($payerId) {
$paymentType = PAYMENT_TYPE_PAYPAL;
} else {
$paymentType = Session::get($invitation->id . 'payment_type');
}
if (!$paymentType) {
$this->error('No-Payment-Type', false, false);
return Redirect::to($invitation->getLink());
}
$accountGateway = $account->getGatewayByType($paymentType);
$gateway = $this->paymentService->createGateway($accountGateway);
// Check for Dwolla payment error
if ($accountGateway->isGateway(GATEWAY_DWOLLA) && Input::get('error')) {
$this->error('Dwolla', Input::get('error_description'), $accountGateway);
return Redirect::to($invitation->getLink());
}
// PayFast transaction referencce
if ($accountGateway->isGateway(GATEWAY_PAYFAST) && Request::has('pt')) {
$token = Request::query('pt');
}
try {
if ($accountGateway->isGateway(GATEWAY_CYBERSOURCE)) {
if (Input::get('decision') == 'ACCEPT') {
$payment = $this->paymentService->createPayment($invitation, $accountGateway, $token, $payerId);
Session::flash('message', trans('texts.applied_payment'));
} else {
$message = Input::get('message') . ': ' . Input::get('invalid_fields');
Session::flash('error', $message);
}
return Redirect::to($invitation->getLink());
} elseif (method_exists($gateway, 'completePurchase')
&& !$accountGateway->isGateway(GATEWAY_TWO_CHECKOUT)
&& !$accountGateway->isGateway(GATEWAY_CHECKOUT_COM)) {
$details = $this->paymentService->getPaymentDetails($invitation, $accountGateway, array());
$response = $this->paymentService->completePurchase($gateway, $accountGateway, $details, $token);
$ref = $response->getTransactionReference() ?: $token;
if ($response->isCancelled()) {
// do nothing
} elseif ($response->isSuccessful()) {
$payment = $this->paymentService->createPayment($invitation, $accountGateway, $ref, $payerId, $details, null, $purchaseResponse);
Session::flash('message', trans('texts.applied_payment'));
} else {
$this->error('offsite', $response->getMessage(), $accountGateway);
}
return Redirect::to($invitation->getLink());
} else {
$payment = $this->paymentService->createPayment($invitation, $accountGateway, $token, $payerId);
Session::flash('message', trans('texts.applied_payment'));
return Redirect::to($invitation->getLink());
}
} catch (\Exception $e) {
$this->error('Offsite-uncaught', false, $accountGateway, $e);
return Redirect::to($invitation->getLink());
}
}
public function store(CreatePaymentRequest $request)
{
$input = $request->input();
@ -760,245 +138,7 @@ class PaymentController extends BaseController
Session::flash('message', $message);
}
return Redirect::to('payments');
return redirect()->to('payments');
}
private function error($type, $error, $accountGateway = false, $exception = false)
{
$message = '';
if ($accountGateway && $accountGateway->gateway) {
$message = $accountGateway->gateway->name . ': ';
}
$message .= $error ?: trans('texts.payment_error');
Session::flash('error', $message);
Utils::logError("Payment Error [{$type}]: " . ($exception ? Utils::getErrorString($exception) : $message), 'PHP', true);
}
public function getBankInfo($routingNumber) {
if (strlen($routingNumber) != 9 || !preg_match('/\d{9}/', $routingNumber)) {
return response()->json([
'message' => 'Invalid routing number',
], 400);
}
$data = PaymentMethod::lookupBankData($routingNumber);
if (is_string($data)) {
return response()->json([
'message' => $data,
], 500);
} elseif (!empty($data)) {
return response()->json($data);
}
return response()->json([
'message' => 'Bank not found',
], 404);
}
public function handlePaymentWebhook($accountKey, $gatewayId)
{
$gatewayId = intval($gatewayId);
$account = Account::where('accounts.account_key', '=', $accountKey)->first();
if (!$account) {
return response()->json([
'message' => 'Unknown account',
], 404);
}
$accountGateway = $account->getGatewayConfig(intval($gatewayId));
if (!$accountGateway) {
return response()->json([
'message' => 'Unknown gateway',
], 404);
}
switch($gatewayId) {
case GATEWAY_STRIPE:
return $this->handleStripeWebhook($accountGateway);
case GATEWAY_WEPAY:
return $this->handleWePayWebhook($accountGateway);
default:
return response()->json([
'message' => 'Unsupported gateway',
], 404);
}
}
protected function handleWePayWebhook($accountGateway) {
$data = Input::all();
$accountId = $accountGateway->account_id;
foreach (array_keys($data) as $key) {
if ('_id' == substr($key, -3)) {
$objectType = substr($key, 0, -3);
$objectId = $data[$key];
break;
}
}
if (!isset($objectType)) {
return response()->json([
'message' => 'Could not find object id parameter',
], 400);
}
if ($objectType == 'credit_card') {
$paymentMethod = PaymentMethod::scope(false, $accountId)->where('source_reference', '=', $objectId)->first();
if (!$paymentMethod) {
return array('message' => 'Unknown payment method');
}
$wepay = \Utils::setupWePay($accountGateway);
$source = $wepay->request('credit_card', array(
'client_id' => WEPAY_CLIENT_ID,
'client_secret' => WEPAY_CLIENT_SECRET,
'credit_card_id' => intval($objectId),
));
if ($source->state == 'deleted') {
$paymentMethod->delete();
} else {
$this->paymentService->convertPaymentMethodFromWePay($source, null, $paymentMethod)->save();
}
return array('message' => 'Processed successfully');
} elseif ($objectType == 'account') {
$config = $accountGateway->getConfig();
if ($config->accountId != $objectId) {
return array('message' => 'Unknown account');
}
$wepay = \Utils::setupWePay($accountGateway);
$wepayAccount = $wepay->request('account', array(
'account_id' => intval($objectId),
));
if ($wepayAccount->state == 'deleted') {
$accountGateway->delete();
} else {
$config->state = $wepayAccount->state;
$accountGateway->setConfig($config);
$accountGateway->save();
}
return array('message' => 'Processed successfully');
} elseif ($objectType == 'checkout') {
$payment = Payment::scope(false, $accountId)->where('transaction_reference', '=', $objectId)->first();
if (!$payment) {
return array('message' => 'Unknown payment');
}
$wepay = \Utils::setupWePay($accountGateway);
$checkout = $wepay->request('checkout', array(
'checkout_id' => intval($objectId),
));
if ($checkout->state == 'refunded') {
$payment->recordRefund();
} elseif (!empty($checkout->refund) && !empty($checkout->refund->amount_refunded) && ($checkout->refund->amount_refunded - $payment->refunded) > 0) {
$payment->recordRefund($checkout->refund->amount_refunded - $payment->refunded);
}
if ($checkout->state == 'captured') {
$payment->markComplete();
} elseif ($checkout->state == 'cancelled') {
$payment->markCancelled();
} elseif ($checkout->state == 'failed') {
$payment->markFailed();
}
return array('message' => 'Processed successfully');
} else {
return array('message' => 'Ignoring event');
}
}
protected function handleStripeWebhook($accountGateway) {
$eventId = Input::get('id');
$eventType= Input::get('type');
$accountId = $accountGateway->account_id;
if (!$eventId) {
return response()->json(['message' => 'Missing event id'], 400);
}
if (!$eventType) {
return response()->json(['message' => 'Missing event type'], 400);
}
$supportedEvents = array(
'charge.failed',
'charge.succeeded',
'customer.source.updated',
'customer.source.deleted',
);
if (!in_array($eventType, $supportedEvents)) {
return array('message' => 'Ignoring event');
}
// Fetch the event directly from Stripe for security
$eventDetails = $this->paymentService->makeStripeCall($accountGateway, 'GET', 'events/'.$eventId);
if (is_string($eventDetails) || !$eventDetails) {
return response()->json([
'message' => $eventDetails ? $eventDetails : 'Could not get event details.',
], 500);
}
if ($eventType != $eventDetails['type']) {
return response()->json(['message' => 'Event type mismatch'], 400);
}
if (!$eventDetails['pending_webhooks']) {
return response()->json(['message' => 'This is not a pending event'], 400);
}
if ($eventType == 'charge.failed' || $eventType == 'charge.succeeded') {
$charge = $eventDetails['data']['object'];
$transactionRef = $charge['id'];
$payment = Payment::scope(false, $accountId)->where('transaction_reference', '=', $transactionRef)->first();
if (!$payment) {
return array('message' => 'Unknown payment');
}
if ($eventType == 'charge.failed') {
if (!$payment->isFailed()) {
$payment->markFailed($charge['failure_message']);
$this->userMailer->sendNotification($payment->user, $payment->invoice, 'payment_failed', $payment);
}
} elseif ($eventType == 'charge.succeeded') {
$payment->markComplete();
} elseif ($eventType == 'charge.refunded') {
$payment->recordRefund($charge['amount_refunded'] / 100 - $payment->refunded);
}
} elseif($eventType == 'customer.source.updated' || $eventType == 'customer.source.deleted') {
$source = $eventDetails['data']['object'];
$sourceRef = $source['id'];
$paymentMethod = PaymentMethod::scope(false, $accountId)->where('source_reference', '=', $sourceRef)->first();
if (!$paymentMethod) {
return array('message' => 'Unknown payment method');
}
if ($eventType == 'customer.source.deleted') {
$paymentMethod->delete();
} elseif ($eventType == 'customer.source.updated') {
$this->paymentService->convertPaymentMethodFromStripe($source, null, $paymentMethod)->save();
}
}
return array('message' => 'Processed successfully');
}
}

View File

@ -0,0 +1,46 @@
<?php namespace App\Http\Requests;
use App\Models\Invitation;
class CreateOnlinePaymentRequest extends Request
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
$account = $this->invitation->account;
$paymentDriver = $account->paymentDriver($this->invitation, $this->gateway_type);
return $paymentDriver->rules();
}
public function sanitize()
{
$input = $this->all();
$invitation = Invitation::with('invoice.invoice_items', 'invoice.client.currency', 'invoice.client.account.currency', 'invoice.client.account.account_gateways.gateway')
->where('invitation_key', '=', $this->invitation_key)
->firstOrFail();
$input['invitation'] = $invitation;
$input['gateway_type'] = session($invitation->id . 'gateway_type');
$this->replace($input);
return $this->all();
}
}

View File

@ -41,15 +41,16 @@ Route::group(['middleware' => 'auth:client'], function() {
Route::get('download/{invitation_key}', 'ClientPortalController@download');
Route::get('view', 'HomeController@viewLogo');
Route::get('approve/{invitation_key}', 'QuoteController@approve');
Route::get('payment/{invitation_key}/{payment_type?}/{source_id?}', 'PaymentController@show_payment');
Route::post('payment/{invitation_key}', 'PaymentController@do_payment');
Route::match(['GET', 'POST'], 'complete', 'PaymentController@offsite_payment');
Route::get('client/paymentmethods', 'ClientPortalController@paymentMethods');
Route::post('client/paymentmethods/verify', 'ClientPortalController@verifyPaymentMethod');
Route::get('client/paymentmethods/add/{payment_type}/{source_id?}', 'ClientPortalController@addPaymentMethod');
Route::post('client/paymentmethods/add/{payment_type}', 'ClientPortalController@postAddPaymentMethod');
Route::post('client/paymentmethods/default', 'ClientPortalController@setDefaultPaymentMethod');
Route::post('client/paymentmethods/{source_id}/remove', 'ClientPortalController@removePaymentMethod');
Route::get('payment/{invitation_key}/{gateway_type?}/{source_id?}', 'OnlinePaymentController@showPayment');
Route::post('payment/{invitation_key}', 'OnlinePaymentController@doPayment');
Route::match(['GET', 'POST'], 'complete/{invitation_key?}/{gateway_type?}', 'OnlinePaymentController@offsitePayment');
Route::get('bank/{routing_number}', 'OnlinePaymentController@getBankInfo');
Route::get('client/payment_methods', 'ClientPortalController@paymentMethods');
Route::post('client/payment_methods/verify', 'ClientPortalController@verifyPaymentMethod');
//Route::get('client/payment_methods/add/{gateway_type}/{source_id?}', 'ClientPortalController@addPaymentMethod');
//Route::post('client/payment_methods/add/{gateway_type}', 'ClientPortalController@postAddPaymentMethod');
Route::post('client/payment_methods/default', 'ClientPortalController@setDefaultPaymentMethod');
Route::post('client/payment_methods/{source_id}/remove', 'ClientPortalController@removePaymentMethod');
Route::get('client/quotes', 'ClientPortalController@quoteIndex');
Route::get('client/invoices', 'ClientPortalController@invoiceIndex');
Route::get('client/invoices/recurring', 'ClientPortalController@recurringInvoiceIndex');
@ -71,11 +72,10 @@ Route::group(['middleware' => 'auth:client'], function() {
});
Route::get('bank/{routing_number}', 'PaymentController@getBankInfo');
Route::post('paymenthook/{accountKey}/{gatewayId}', 'PaymentController@handlePaymentWebhook');
Route::get('license', 'PaymentController@show_license_payment');
Route::post('license', 'PaymentController@do_license_payment');
Route::get('claim_license', 'PaymentController@claim_license');
Route::get('license', 'NinjaController@show_license_payment');
Route::post('license', 'NinjaController@do_license_payment');
Route::get('claim_license', 'NinjaController@claim_license');
Route::post('signup/validate', 'AccountController@checkEmail');
Route::post('signup/submit', 'AccountController@submitSignup');
@ -557,7 +557,6 @@ if (!defined('CONTACT_EMAIL')) {
define('PAYMENT_LIBRARY_PHP_PAYMENTS', 2);
define('GATEWAY_AUTHORIZE_NET', 1);
define('GATEWAY_AUTHORIZE_NET_SIM', 2);
define('GATEWAY_EWAY', 4);
define('GATEWAY_MOLLIE', 9);
define('GATEWAY_PAYFAST', 13);
@ -661,7 +660,7 @@ if (!defined('CONTACT_EMAIL')) {
define('PAYMENT_TYPE_EUROCARD', 11);
define('PAYMENT_TYPE_NOVA', 12);
define('PAYMENT_TYPE_CREDIT_CARD_OTHER', 13);
define('PAYMENT_TYPE_ID_PAYPAL', 14);
define('PAYMENT_TYPE_PAYPAL', 14);
define('PAYMENT_TYPE_CARTE_BLANCHE', 17);
define('PAYMENT_TYPE_UNIONPAY', 18);
define('PAYMENT_TYPE_JCB', 19);
@ -674,18 +673,12 @@ if (!defined('CONTACT_EMAIL')) {
define('PAYMENT_METHOD_STATUS_VERIFICATION_FAILED', 'verification_failed');
define('PAYMENT_METHOD_STATUS_VERIFIED', 'verified');
define('PAYMENT_TYPE_PAYPAL', 'PAYMENT_TYPE_PAYPAL');
define('PAYMENT_TYPE_STRIPE', 'PAYMENT_TYPE_STRIPE');
define('PAYMENT_TYPE_STRIPE_CREDIT_CARD', 'PAYMENT_TYPE_STRIPE_CREDIT_CARD');
define('PAYMENT_TYPE_STRIPE_ACH', 'PAYMENT_TYPE_STRIPE_ACH');
define('PAYMENT_TYPE_BRAINTREE_PAYPAL', 'PAYMENT_TYPE_BRAINTREE_PAYPAL');
define('PAYMENT_TYPE_WEPAY_ACH', 'PAYMENT_TYPE_WEPAY_ACH');
define('PAYMENT_TYPE_CREDIT_CARD', 'PAYMENT_TYPE_CREDIT_CARD');
define('PAYMENT_TYPE_DIRECT_DEBIT', 'PAYMENT_TYPE_DIRECT_DEBIT');
define('PAYMENT_TYPE_BITCOIN', 'PAYMENT_TYPE_BITCOIN');
define('PAYMENT_TYPE_DWOLLA', 'PAYMENT_TYPE_DWOLLA');
define('PAYMENT_TYPE_TOKEN', 'PAYMENT_TYPE_TOKEN');
define('PAYMENT_TYPE_ANY', 'PAYMENT_TYPE_ANY');
define('GATEWAY_TYPE_CREDIT_CARD', 'credit_card');
define('GATEWAY_TYPE_BANK_TRANSFER', 'bank_transfer');
define('GATEWAY_TYPE_PAYPAL', 'paypal');
define('GATEWAY_TYPE_BITCOIN', 'bitcoin');
define('GATEWAY_TYPE_DWOLLA', 'dwolla');
define('GATEWAY_TYPE_TOKEN', 'token');
define('REMINDER1', 'reminder1');
define('REMINDER2', 'reminder2');

View File

@ -28,7 +28,7 @@ class CreditListener
$credit = Credit::createNew();
$credit->client_id = $payment->client_id;
$credit->credit_date = Carbon::now()->toDateTimeString();
$credit->balance = $credit->amount = $payment->amount - $payment->refunded;
$credit->balance = $credit->amount = $payment->getCompletedAmount();
$credit->private_notes = $payment->transaction_reference;
$credit->save();
}

View File

@ -61,7 +61,7 @@ class InvoiceListener
{
$payment = $event->payment;
$invoice = $payment->invoice;
$adjustment = $payment->amount - $payment->refunded;
$adjustment = $payment->getCompletedAmount();
$invoice->updateBalances($adjustment);
$invoice->updatePaidStatus();
@ -91,7 +91,7 @@ class InvoiceListener
{
$payment = $event->payment;
$invoice = $payment->invoice;
$adjustment = $payment->amount - $payment->refunded;
$adjustment = $payment->getCompletedAmount();
$invoice->updateBalances($adjustment);
$invoice->updatePaidStatus();
@ -105,7 +105,7 @@ class InvoiceListener
$payment = $event->payment;
$invoice = $payment->invoice;
$adjustment = ($payment->amount - $payment->refunded) * -1;
$adjustment = $payment->getCompletedAmount() * -1;
$invoice->updateBalances($adjustment);
$invoice->updatePaidStatus();

View File

@ -384,37 +384,44 @@ class Account extends Eloquent
return $format;
}
public function getGatewayByType($type = PAYMENT_TYPE_ANY, $exceptFor = null)
/*
public function defaultGatewayType()
{
if ($type == PAYMENT_TYPE_STRIPE_ACH || $type == PAYMENT_TYPE_STRIPE_CREDIT_CARD) {
$type = PAYMENT_TYPE_STRIPE;
$accountGateway = $this->account_gateways[0];
$paymentDriver = $accountGateway->paymentDriver();
return $paymentDriver->gatewayTypes()[0];
}
*/
public function getGatewayByType($type = false)
{
if ( ! $this->relationLoaded('account_gateways')) {
$this->load('account_gateways');
}
if ($type == PAYMENT_TYPE_WEPAY_ACH) {
return $this->getGatewayConfig(GATEWAY_WEPAY);
foreach ($this->account_gateways as $accountGateway) {
if ( ! $type) {
return $accountGateway;
}
foreach ($this->account_gateways as $gateway) {
if ($exceptFor && ($gateway->id == $exceptFor->id)) {
continue;
}
$paymentDriver = $accountGateway->paymentDriver();
if (!$type || $type == PAYMENT_TYPE_ANY) {
return $gateway;
} elseif ($gateway->isPaymentType($type)) {
return $gateway;
} elseif ($type == PAYMENT_TYPE_CREDIT_CARD && $gateway->isPaymentType(PAYMENT_TYPE_STRIPE)) {
return $gateway;
} elseif ($type == PAYMENT_TYPE_DIRECT_DEBIT && $gateway->getAchEnabled()) {
return $gateway;
} elseif ($type == PAYMENT_TYPE_PAYPAL && $gateway->getPayPalEnabled()) {
return $gateway;
if ($paymentDriver->handles($type)) {
return $accountGateway;
}
}
return false;
}
public function paymentDriver($invitation = false, $gatewayType = false)
{
$accountGateway = $this->getGatewayByType($gatewayType);
return $accountGateway->paymentDriver($invitation, $gatewayType);
}
public function gatewayIds()
{
return $this->account_gateways()->pluck('gateway_id')->toArray();
@ -1437,18 +1444,6 @@ class Account extends Eloquent
public function getFontFolders(){
return array_map(function($item){return $item['folder'];}, $this->getFontsData());
}
public function canAddGateway($type){
if ($type == PAYMENT_TYPE_STRIPE) {
$type == PAYMENT_TYPE_CREDIT_CARD;
}
if($this->getGatewayByType($type)) {
return false;
}
return true;
}
}
Account::updated(function ($account)

View File

@ -3,10 +3,14 @@
use Crypt;
use App\Models\Gateway;
use Illuminate\Database\Eloquent\SoftDeletes;
use Laracasts\Presenter\PresentableTrait;
class AccountGateway extends EntityModel
{
use SoftDeletes;
use PresentableTrait;
protected $presenter = 'App\Ninja\Presenters\AccountGatewayPresenter';
protected $dates = ['deleted_at'];
public function getEntityType()
@ -33,14 +37,18 @@ class AccountGateway extends EntityModel
return $arrayOfImages;
}
public function getPaymentType()
public function paymentDriver($invitation = false, $gatewayType = false)
{
return Gateway::getPaymentType($this->gateway_id);
}
$folder = "App\\Ninja\\PaymentDrivers\\";
$class = $folder . $this->gateway->provider . 'PaymentDriver';
$class = str_replace('_', '', $class);
public function isPaymentType($type)
{
return $this->getPaymentType() == $type;
if (class_exists($class)) {
return new $class($this, $invitation, $gatewayType);
} else {
$baseClass = $folder . "BasePaymentDriver";
return new $baseClass($this, $invitation, $gatewayType);
}
}
public function isGateway($gatewayId)
@ -131,4 +139,3 @@ class AccountGateway extends EntityModel
return \URL::to(env('WEBHOOK_PREFIX','').'paymenthook/'.$account->account_key.'/'.$this->gateway_id.env('WEBHOOK_SUFFIX',''));
}
}

View File

@ -9,17 +9,54 @@ class AccountGatewayToken extends Eloquent
protected $dates = ['deleted_at'];
public $timestamps = true;
protected $casts = [
'uses_local_payment_methods' => 'boolean',
];
protected $casts = [];
public function payment_methods()
{
return $this->hasMany('App\Models\PaymentMethod');
}
public function account_gateway()
{
return $this->belongsTo('App\Models\AccountGateway');
}
public function default_payment_method()
{
return $this->hasOne('App\Models\PaymentMethod', 'id', 'default_payment_method_id');
}
public function autoBillLater()
{
return $this->default_payment_method->requiresDelayedAutoBill();
}
public function scopeClientAndGateway($query, $clientId, $accountGatewayId)
{
$query->where('client_id', '=', $clientId)
->where('account_gateway_id', '=', $accountGatewayId);
return $query;
}
public function gatewayName()
{
return $this->account_gateway->gateway->name;
}
public function gatewayLink()
{
$accountGateway = $this->account_gateway;
if ($accountGateway->gateway_id == GATEWAY_STRIPE) {
return "https://dashboard.stripe.com/customers/{$this->token}";
} elseif ($accountGateway->gateway_id == GATEWAY_BRAINTREE) {
$merchantId = $accountGateway->getConfig()->merchantId;
$testMode = $accountGateway->getConfig()->testMode;
return $testMode ? "https://sandbox.braintreegateway.com/merchants/{$merchantId}/customers/{$this->token}" : "https://www.braintreegateway.com/merchants/{$merchantId}/customers/{$this->token}";
} else {
return false;
}
}
}

View File

@ -5,6 +5,7 @@ use DB;
use Carbon;
use Laracasts\Presenter\PresentableTrait;
use Illuminate\Database\Eloquent\SoftDeletes;
use App\Models\AccountGatewayToken;
class Client extends EntityModel
{
@ -260,49 +261,28 @@ class Client extends EntityModel
}
}
public function getGatewayToken(&$accountGateway = null, &$token = null)
public function getGatewayToken()
{
$account = $this->account;
if ( ! $account->relationLoaded('account_gateways')) {
$account->load('account_gateways');
}
if (!count($account->account_gateways)) {
return false;
}
if (!$accountGateway){
$accountGateway = $account->getTokenGateway();
}
$accountGateway = $this->account->getGatewayByType(GATEWAY_TYPE_TOKEN);
if ( ! $accountGateway) {
return false;
}
$token = AccountGatewayToken::where('client_id', '=', $this->id)
->where('account_gateway_id', '=', $accountGateway->id)->first();
return $token ? $token->token : false;
return AccountGatewayToken::clientAndGateway($this->id, $accountGateway->id)->first();
}
public function getGatewayLink(&$accountGateway = null)
public function autoBillLater()
{
$token = $this->getGatewayToken($accountGateway);
if (!$token) {
return false;
if ($token = $this->getGatewayToken()) {
if ($this->account->auto_bill_on_due_date) {
return true;
}
if ($accountGateway->gateway_id == GATEWAY_STRIPE) {
return "https://dashboard.stripe.com/customers/{$token}";
} elseif ($accountGateway->gateway_id == GATEWAY_BRAINTREE) {
$merchantId = $accountGateway->getConfig()->merchantId;
$testMode = $accountGateway->getConfig()->testMode;
return $testMode ? "https://sandbox.braintreegateway.com/merchants/{$merchantId}/customers/{$token}" : "https://www.braintreegateway.com/merchants/{$merchantId}/customers/{$token}";
} else {
return false;
return $token->autoBillLater();
}
return false;
}
public function getAmount()
@ -323,6 +303,19 @@ class Client extends EntityModel
return $this->account->currency_id ?: DEFAULT_CURRENCY;
}
public function getCurrencyCode()
{
if ($this->currency) {
return $this->currency->code;
}
if (!$this->account) {
$this->load('account');
}
return $this->account->currency ? $this->account->currency->code : 'USD';
}
public function getCounter($isQuote)
{
return $isQuote ? $this->quote_number_counter : $this->invoice_number_counter;

View File

@ -8,6 +8,15 @@ class Gateway extends Eloquent
{
public $timestamps = true;
public static $gatewayTypes = [
GATEWAY_TYPE_CREDIT_CARD,
GATEWAY_TYPE_BANK_TRANSFER,
GATEWAY_TYPE_PAYPAL,
GATEWAY_TYPE_BITCOIN,
GATEWAY_TYPE_DWOLLA,
GATEWAY_TYPE_TOKEN,
];
// these will appear in the primary gateway select
// the rest are shown when selecting 'more options'
public static $preferred = [
@ -26,16 +35,6 @@ class Gateway extends Eloquent
GATEWAY_DWOLLA,
];
// TODO remove this
public static $paymentTypes = [
PAYMENT_TYPE_STRIPE,
PAYMENT_TYPE_CREDIT_CARD,
PAYMENT_TYPE_PAYPAL,
PAYMENT_TYPE_BITCOIN,
PAYMENT_TYPE_DIRECT_DEBIT,
PAYMENT_TYPE_DWOLLA,
];
public static $hiddenFields = [
// PayPal
'headerImageUrl',
@ -103,21 +102,11 @@ class Gateway extends Eloquent
}
}
/*
public static function getPaymentTypeLinks() {
$data = [];
foreach (self::$paymentTypes as $type) {
$data[] = Utils::toCamelCase(strtolower(str_replace('PAYMENT_TYPE_', '', $type)));
}
return $data;
}
*/
public function getHelp()
{
$link = '';
if ($this->id == GATEWAY_AUTHORIZE_NET || $this->id == GATEWAY_AUTHORIZE_NET_SIM) {
if ($this->id == GATEWAY_AUTHORIZE_NET) {
$link = 'http://reseller.authorize.net/application/?id=5560364';
} elseif ($this->id == GATEWAY_PAYPAL_EXPRESS) {
$link = 'https://www.paypal.com/us/cgi-bin/webscr?cmd=_login-api-run';
@ -141,24 +130,4 @@ class Gateway extends Eloquent
{
return Omnipay::create($this->provider)->getDefaultParameters();
}
public static function getPaymentType($gatewayId) {
if ($gatewayId == GATEWAY_PAYPAL_EXPRESS) {
return PAYMENT_TYPE_PAYPAL;
} else if ($gatewayId == GATEWAY_BITPAY) {
return PAYMENT_TYPE_BITCOIN;
} else if ($gatewayId == GATEWAY_DWOLLA) {
return PAYMENT_TYPE_DWOLLA;
} else if ($gatewayId == GATEWAY_GOCARDLESS) {
return PAYMENT_TYPE_DIRECT_DEBIT;
} else if ($gatewayId == GATEWAY_STRIPE) {
return PAYMENT_TYPE_STRIPE;
} else {
return PAYMENT_TYPE_CREDIT_CARD;
}
}
public static function getPrettyPaymentType($gatewayId) {
return trans('texts.' . strtolower(Gateway::getPaymentType($gatewayId)));
}
}

View File

@ -890,13 +890,13 @@ class Invoice extends EntityModel implements BalanceAffecting
if ($this->tax_name1) {
$invoiceTaxAmount = round($taxable * ($this->tax_rate1 / 100), 2);
$invoicePaidAmount = $this->amount && $invoiceTaxAmount ? ($paidAmount / $this->amount * $invoiceTaxAmount) : 0;
$invoicePaidAmount = floatVal($this->amount) && $invoiceTaxAmount ? ($paidAmount / $this->amount * $invoiceTaxAmount) : 0;
$this->calculateTax($taxes, $this->tax_name1, $this->tax_rate1, $invoiceTaxAmount, $invoicePaidAmount);
}
if ($this->tax_name2) {
$invoiceTaxAmount = round($taxable * ($this->tax_rate2 / 100), 2);
$invoicePaidAmount = $this->amount && $invoiceTaxAmount ? ($paidAmount / $this->amount * $invoiceTaxAmount) : 0;
$invoicePaidAmount = floatVal($this->amount) && $invoiceTaxAmount ? ($paidAmount / $this->amount * $invoiceTaxAmount) : 0;
$this->calculateTax($taxes, $this->tax_name2, $this->tax_rate2, $invoiceTaxAmount, $invoicePaidAmount);
}
@ -905,13 +905,13 @@ class Invoice extends EntityModel implements BalanceAffecting
if ($invoiceItem->tax_name1) {
$itemTaxAmount = round($taxable * ($invoiceItem->tax_rate1 / 100), 2);
$itemPaidAmount = $this->amount && $itemTaxAmount ? ($paidAmount / $this->amount * $itemTaxAmount) : 0;
$itemPaidAmount = floatVal($this->amount) && $itemTaxAmount ? ($paidAmount / $this->amount * $itemTaxAmount) : 0;
$this->calculateTax($taxes, $invoiceItem->tax_name1, $invoiceItem->tax_rate1, $itemTaxAmount, $itemPaidAmount);
}
if ($invoiceItem->tax_name2) {
$itemTaxAmount = round($taxable * ($invoiceItem->tax_rate2 / 100), 2);
$itemPaidAmount = $this->amount && $itemTaxAmount ? ($paidAmount / $this->amount * $itemTaxAmount) : 0;
$itemPaidAmount = floatVal($this->amount) && $itemTaxAmount ? ($paidAmount / $this->amount * $itemTaxAmount) : 0;
$this->calculateTax($taxes, $invoiceItem->tax_name2, $invoiceItem->tax_rate2, $itemTaxAmount, $itemPaidAmount);
}
}

View File

@ -118,7 +118,10 @@ class Payment extends EntityModel
public function recordRefund($amount = null)
{
if (!$this->isRefunded() && !$this->isVoided()) {
if ($this->isRefunded() || $this->isVoided()) {
return false;
}
if (!$amount) {
$amount = $this->amount;
}
@ -133,18 +136,23 @@ class Payment extends EntityModel
Event::fire(new PaymentWasRefunded($this, $refund_change));
}
}
return true;
}
public function markVoided()
{
if (!$this->isVoided() && !$this->isPartiallyRefunded() && !$this->isRefunded()) {
if ($this->isVoided() || $this->isPartiallyRefunded() || $this->isRefunded()) {
return false;
}
$this->refunded = $this->amount;
$this->payment_status_id = PAYMENT_STATUS_VOIDED;
$this->save();
Event::fire(new PaymentWasVoided($this));
}
return true;
}
public function markComplete()

View File

@ -8,31 +8,11 @@ class PaymentMethod extends EntityModel
{
use SoftDeletes;
protected $dates = ['deleted_at'];
public $timestamps = true;
protected $dates = ['deleted_at'];
protected $hidden = ['id'];
public static function createNew($accountGatewayToken = null)
{
$entity = new PaymentMethod();
$entity->account_id = $accountGatewayToken->account_id;
$entity->account_gateway_token_id = $accountGatewayToken->id;
$lastEntity = static::scope(false, $entity->account_id);
$lastEntity = $lastEntity->orderBy('public_id', 'DESC')
->first();
if ($lastEntity) {
$entity->public_id = $lastEntity->public_id + 1;
} else {
$entity->public_id = 1;
}
return $entity;
}
public function account()
{
return $this->belongsTo('App\Models\Account');
@ -86,15 +66,25 @@ class PaymentMethod extends EntityModel
return $value ? str_pad($value, 4, '0', STR_PAD_LEFT) : null;
}
public function scopeScope($query, $publicId = false, $accountId = false, $accountGatewayTokenId = false)
public function scopeClientId($query, $clientId)
{
$query = parent::scopeScope($query, $publicId, $accountId);
if ($accountGatewayTokenId) {
$query->where($this->getTable() . '.account_gateway_token_id', '=', $accountGatewayTokenId);
return $query->with(['contact' => function($query) use ($clientId) {
return $query->whereClientId($clientId);
}]);
}
return $query;
public function scopeIsBankAccount($query, $isBank)
{
if ($isBank) {
$query->where('payment_type_id', '=', PAYMENT_TYPE_ACH);
} else {
$query->where('payment_type_id', '!=', PAYMENT_TYPE_ACH);
}
}
public function imageUrl()
{
return url(sprintf('/images/credit_cards/%s.png', str_replace(' ', '', strtolower($this->payment_type->name))));
}
public static function lookupBankData($routingNumber) {

View File

@ -280,6 +280,9 @@ class User extends Model implements AuthenticatableContract, AuthorizableContrac
} else {
$bitmask = 0;
foreach($value as $permission){
if ( ! $permission) {
continue;
}
$bitmask = $bitmask | static::$all_permissions[$permission];
}

View File

@ -49,12 +49,6 @@ class AccountGatewayDatatable extends EntityDatatable
}
}
],
[
'payment_type',
function ($model) {
return Gateway::getPrettyPaymentType($model->gateway_id);
}
],
];
}

View File

@ -107,20 +107,6 @@ class ContactMailer extends Mailer
return $response;
}
private function createAutoBillNotifyString($paymentMethod) {
if ($paymentMethod->payment_type_id == PAYMENT_TYPE_DIRECT_DEBIT) {
$paymentMethodString = trans('texts.auto_bill_payment_method_bank', ['bank'=>$paymentMethod->getBankName(), 'last4'=>$paymentMethod->last4]);
} elseif ($paymentMethod->payment_type_id == PAYMENT_TYPE_ID_PAYPAL) {
$paymentMethodString = trans('texts.auto_bill_payment_method_paypal', ['email'=>$paymentMethod->email]);
} else {
$code = str_replace(' ', '', strtolower($paymentMethod->payment_type->name));
$cardType = trans("texts.card_" . $code);
$paymentMethodString = trans('texts.auto_bill_payment_method_credit_card', ['type'=>$cardType,'last4'=>$paymentMethod->last4]);
}
return trans('texts.auto_bill_notification', ['payment_method'=>$paymentMethodString]);
}
private function sendInvitation($invitation, $invoice, $body, $subject, $pdfString, $documentStrings)
{
$client = $invoice->client;
@ -152,10 +138,12 @@ class ContactMailer extends Mailer
'amount' => $invoice->getRequestedAmount()
];
if ($invoice->autoBillPaymentMethod) {
/*
if ($client->autoBillLater()) {
// Let the client know they'll be billed later
$variables['autobill'] = $this->createAutoBillNotifyString($invoice->autoBillPaymentMethod);
}
*/
if (empty($invitation->contact->password) && $account->hasFeature(FEATURE_CLIENT_PORTAL_PASSWORD) && $account->enable_portal_password && $account->send_portal_password) {
// The contact needs a password
@ -293,4 +281,20 @@ class ContactMailer extends Mailer
$this->sendTo($email, CONTACT_EMAIL, CONTACT_NAME, $subject, $view, $data);
}
/*
private function createAutoBillNotifyString($paymentMethod) {
if ($paymentMethod->payment_type_id == PAYMENT_TYPE_DIRECT_DEBIT) {
$paymentMethodString = trans('texts.auto_bill_payment_method_bank', ['bank'=>$paymentMethod->getBankName(), 'last4'=>$paymentMethod->last4]);
} elseif ($paymentMethod->payment_type_id == PAYMENT_TYPE_PAYPAL) {
$paymentMethodString = trans('texts.auto_bill_payment_method_paypal', ['email'=>$paymentMethod->email]);
} else {
$code = str_replace(' ', '', strtolower($paymentMethod->payment_type->name));
$cardType = trans("texts.card_" . $code);
$paymentMethodString = trans('texts.auto_bill_payment_method_credit_card', ['type'=>$cardType,'last4'=>$paymentMethod->last4]);
}
return trans('texts.auto_bill_notification', ['payment_method'=>$paymentMethodString]);
}
*/
}

View File

@ -0,0 +1,6 @@
<?php namespace App\Ninja\PaymentDrivers;
class AuthorizeNetAIMPaymentDriver extends BasePaymentDriver
{
protected $transactionReferenceParam = 'refId';
}

View File

@ -0,0 +1,796 @@
<?php namespace App\Ninja\PaymentDrivers;
use URL;
use Session;
use Request;
use Omnipay;
use Exception;
use CreditCard;
use App\Models\AccountGatewayToken;
use App\Models\Payment;
use App\Models\PaymentMethod;
use App\Models\Country;
class BasePaymentDriver
{
public $invitation;
public $accountGateway;
protected $gatewayType;
protected $gateway;
protected $customer;
protected $sourceId;
protected $input;
protected $customerResponse;
protected $tokenResponse;
protected $purchaseResponse;
protected $sourceReferenceParam;
protected $customerReferenceParam;
protected $transactionReferenceParam;
public function __construct($accountGateway, $invitation = false, $gatewayType = false)
{
$this->accountGateway = $accountGateway;
$this->invitation = $invitation;
$this->gatewayType = $gatewayType ?: $this->gatewayTypes()[0];
}
public function isGateway($gatewayId)
{
return $this->accountGateway->gateway_id == $gatewayId;
}
protected function isGatewayType($gatewayType)
{
return $this->gatewayType === $gatewayType;
}
protected function gatewayTypes()
{
return [
GATEWAY_TYPE_CREDIT_CARD
];
}
public function handles($type)
{
return in_array($type, $this->gatewayTypes());
}
// when set to true we won't pass the card details with the form
public function tokenize()
{
return false;
}
// set payment method as pending until confirmed
public function isTwoStep()
{
return false;
}
public function providerName()
{
return strtolower($this->accountGateway->gateway->provider);
}
protected function invoice()
{
return $this->invitation->invoice;
}
protected function contact()
{
return $this->invitation->contact;
}
protected function client()
{
return $this->invoice()->client;
}
protected function account()
{
return $this->client()->account;
}
public function startPurchase($input = false, $sourceId = false)
{
$this->input = $input;
$this->sourceId = $sourceId;
Session::put('invitation_key', $this->invitation->invitation_key);
Session::put($this->invitation->id . 'gateway_type', $this->gatewayType);
Session::put($this->invitation->id . 'payment_ref', $this->invoice()->id . '_' . uniqid());
$gateway = $this->accountGateway->gateway;
if ($this->isGatewayType(GATEWAY_TYPE_TOKEN) || $gateway->is_offsite) {
if (Session::has('error')) {
Session::reflash();
} else {
$this->completeOnsitePurchase();
Session::flash('message', trans('texts.applied_payment'));
}
return redirect()->to('view/' . $this->invitation->invitation_key);
}
$data = [
'accountGateway' => $this->accountGateway,
'acceptedCreditCardTypes' => $this->accountGateway->getCreditcardTypes(),
'gateway' => $gateway,
'showAddress' => $this->accountGateway->show_address,
'showBreadcrumbs' => false,
'url' => 'payment/' . $this->invitation->invitation_key,
'amount' => $this->invoice()->getRequestedAmount(),
'invoiceNumber' => $this->invoice()->invoice_number,
'client' => $this->client(),
'contact' => $this->invitation->contact,
'gatewayType' => $this->gatewayType,
'currencyId' => $this->client()->getCurrencyId(),
'currencyCode' => $this->client()->getCurrencyCode(),
'account' => $this->account(),
'sourceId' => $sourceId,
'clientFontUrl' => $this->account()->getFontsUrl(),
'tokenize' => $this->tokenize(),
'transactionToken' => $this->createTransactionToken(),
];
return view($this->paymentView(), $data);
}
// check if a custom view exists for this provider
protected function paymentView()
{
$file = sprintf('%s/views/payments/%s/%s.blade.php', resource_path(), $this->providerName(), $this->gatewayType);
if (file_exists($file)) {
return sprintf('payments.%s/%s', $this->providerName(), $this->gatewayType);
} else {
return sprintf('payments.%s', $this->gatewayType);
}
}
// check if a custom partial exists for this provider
public function partialView()
{
$file = sprintf('%s/views/payments/%s/partial.blade.php', resource_path(), $this->providerName());
if (file_exists($file)) {
return sprintf('payments.%s.partial', $this->providerName());
} else {
return false;
}
}
public function rules()
{
$rules = [];
if ($this->isGatewayType(GATEWAY_TYPE_CREDIT_CARD)) {
$rules = array_merge($rules, [
'first_name' => 'required',
'last_name' => 'required',
]);
// TODO check this is always true
if ( ! $this->tokenize()) {
$rules = array_merge($rules, [
'card_number' => 'required',
'expiration_month' => 'required',
'expiration_year' => 'required',
'cvv' => 'required',
]);
}
if ($this->accountGateway->show_address) {
$rules = array_merge($rules, [
'address1' => 'required',
'city' => 'required',
'state' => 'required',
'postal_code' => 'required',
'country_id' => 'required',
]);
}
}
return $rules;
}
protected function gateway()
{
if ($this->gateway) {
return $this->gateway;
}
$this->gateway = Omnipay::create($this->accountGateway->gateway->provider);
$this->gateway->initialize((array) $this->accountGateway->getConfig());
return $this->gateway;
}
public function completeOnsitePurchase($input = false, $paymentMethod = false)
{
$this->input = count($input) ? $input : false;
$gateway = $this->gateway();
if ($input) {
$this->updateAddress();
}
// load or create token
if ($this->isGatewayType(GATEWAY_TYPE_TOKEN)) {
if ( ! $paymentMethod) {
$paymentMethod = PaymentMethod::clientId($this->client()->id)
->wherePublicId($this->sourceId)
->firstOrFail();
}
} elseif ($this->shouldCreateToken()) {
$paymentMethod = $this->createToken();
}
if ($this->isTwoStep()) {
return;
}
// prepare and process payment
$data = $this->paymentDetails($paymentMethod);
$response = $gateway->purchase($data)->send();
$this->purchaseResponse = (array) $response->getData();
// parse the transaction reference
if ($this->transactionReferenceParam) {
$ref = $this->purchaseResponse[$this->transactionReferenceParam];
} else {
$ref = $response->getTransactionReference();
}
// wrap up
if ($response->isSuccessful() && $ref) {
$payment = $this->createPayment($ref, $paymentMethod);
// TODO move this to stripe driver
if ($this->invitation->invoice->account->account_key == NINJA_ACCOUNT_KEY) {
Session::flash('trackEventCategory', '/account');
Session::flash('trackEventAction', '/buy_pro_plan');
Session::flash('trackEventAmount', $payment->amount);
}
return $payment;
} elseif ($response->isRedirect()) {
$this->invitation->transaction_reference = $ref;
$this->invitation->save();
//Session::put('transaction_reference', $ref);
Session::save();
$response->redirect();
} else {
throw new Exception($response->getMessage() ?: trans('texts.payment_error'));
}
}
private function updateAddress()
{
if ( ! $this->isGatewayType(GATEWAY_TYPE_CREDIT_CARD)) {
return;
}
if ( ! $this->accountGateway->show_address || ! $this->accountGateway->update_address) {
return;
}
$client = $this->client();
$client->address1 = trim($this->input['address1']);
$client->address2 = trim($this->input['address2']);
$client->city = trim($this->input['city']);
$client->state = trim($this->input['state']);
$client->postal_code = trim($this->input['postal_code']);
$client->country_id = trim($this->input['country_id']);
$client->save();
}
protected function paymentDetails($paymentMethod = false)
{
$invoice = $this->invoice();
$completeUrl = url('complete/' . $this->invitation->invitation_key . '/' . $this->gatewayType);
$data = [
'amount' => $invoice->getRequestedAmount(),
'currency' => $invoice->getCurrencyCode(),
'returnUrl' => $completeUrl,
'cancelUrl' => $this->invitation->getLink(),
'description' => trans('texts.' . $invoice->getEntityType()) . " {$invoice->invoice_number}",
'transactionId' => $invoice->invoice_number,
'transactionType' => 'Purchase',
'ip' => Request::ip()
];
if ($paymentMethod) {
if ($this->customerReferenceParam) {
$data[$this->customerReferenceParam] = $paymentMethod->account_gateway_token->token;
}
$data[$this->sourceReferenceParam] = $paymentMethod->source_reference;
} elseif ($this->input) {
$data['card'] = new CreditCard($this->paymentDetailsFromInput($this->input));
} else {
$data['card'] = new CreditCard($this->paymentDetailsFromClient());
}
return $data;
}
private function paymentDetailsFromInput($input)
{
$invoice = $this->invoice();
$client = $this->client();
$data = [
'company' => $client->getDisplayName(),
'firstName' => isset($input['first_name']) ? $input['first_name'] : null,
'lastName' => isset($input['last_name']) ? $input['last_name'] : null,
'email' => isset($input['email']) ? $input['email'] : null,
'number' => isset($input['card_number']) ? $input['card_number'] : null,
'expiryMonth' => isset($input['expiration_month']) ? $input['expiration_month'] : null,
'expiryYear' => isset($input['expiration_year']) ? $input['expiration_year'] : null,
];
// allow space until there's a setting to disable
if (isset($input['cvv']) && $input['cvv'] != ' ') {
$data['cvv'] = $input['cvv'];
}
if (isset($input['address1'])) {
// TODO use cache instead
$country = Country::find($input['country_id']);
$data = array_merge($data, [
'billingAddress1' => $input['address1'],
'billingAddress2' => $input['address2'],
'billingCity' => $input['city'],
'billingState' => $input['state'],
'billingPostcode' => $input['postal_code'],
'billingCountry' => $country->iso_3166_2,
'shippingAddress1' => $input['address1'],
'shippingAddress2' => $input['address2'],
'shippingCity' => $input['city'],
'shippingState' => $input['state'],
'shippingPostcode' => $input['postal_code'],
'shippingCountry' => $country->iso_3166_2
]);
}
return $data;
}
public function paymentDetailsFromClient()
{
$invoice = $this->invoice();
$client = $this->client();
$contact = $this->invitation->contact ?: $client->contacts()->first();
return [
'email' => $contact->email,
'company' => $client->getDisplayName(),
'firstName' => $contact->first_name,
'lastName' => $contact->last_name,
'billingAddress1' => $client->address1,
'billingAddress2' => $client->address2,
'billingCity' => $client->city,
'billingPostcode' => $client->postal_code,
'billingState' => $client->state,
'billingCountry' => $client->country ? $client->country->iso_3166_2 : '',
'billingPhone' => $contact->phone,
'shippingAddress1' => $client->address1,
'shippingAddress2' => $client->address2,
'shippingCity' => $client->city,
'shippingPostcode' => $client->postal_code,
'shippingState' => $client->state,
'shippingCountry' => $client->country ? $client->country->iso_3166_2 : '',
'shippingPhone' => $contact->phone,
];
}
protected function shouldCreateToken()
{
if ($this->isGatewayType(GATEWAY_TYPE_BANK_TRANSFER)) {
return true;
}
if ( ! $this->handles(GATEWAY_TYPE_TOKEN)) {
return false;
}
if ($this->account()->token_billing_type_id == TOKEN_BILLING_ALWAYS) {
return true;
}
return boolval(array_get($this->input, 'token_billing'));
}
/*
protected function tokenDetails()
{
$details = [];
if ($customer = $this->customer()) {
$details['customerReference'] = $customer->token;
}
return $details;
}
*/
public function customer($clientId = false)
{
if ($this->customer) {
return $this->customer;
}
if ( ! $clientId) {
$clientId = $this->client()->id;
}
$this->customer = AccountGatewayToken::clientAndGateway($clientId, $this->accountGateway->id)
->with('payment_methods')
->first();
if ($this->customer) {
$this->customer = $this->checkCustomerExists($this->customer) ? $this->customer : null;
}
return $this->customer;
}
protected function checkCustomerExists($customer)
{
return true;
}
public function verifyBankAccount($client, $publicId, $amount1, $amount2)
{
throw new Exception('verifyBankAccount not implemented');
}
public function removePaymentMethod($paymentMethod)
{
$paymentMethod->delete();
}
// Some gateways (ie, Checkout.com and Braintree) require generating a token before paying for the invoice
public function createTransactionToken()
{
return null;
}
public function createToken()
{
$account = $this->account();
if ( ! $customer = $this->customer()) {
$customer = new AccountGatewayToken();
$customer->account_id = $account->id;
$customer->contact_id = $this->invitation->contact_id;
$customer->account_gateway_id = $this->accountGateway->id;
$customer->client_id = $this->client()->id;
$customer = $this->creatingCustomer($customer);
$customer->save();
}
/*
// archive the old payment method
$paymentMethod = PaymentMethod::clientId($this->client()->id)
->isBankAccount($this->isGatewayType(GATEWAY_TYPE_BANK_TRANSFER))
->first();
if ($paymentMethod) {
$paymentMethod->delete();
}
*/
$paymentMethod = $this->createPaymentMethod($customer);
if ($paymentMethod && ! $customer->default_payment_method_id) {
$customer->default_payment_method_id = $paymentMethod->id;
$customer->save();
}
return $paymentMethod;
}
protected function creatingCustomer($customer)
{
return $customer;
}
public function createPaymentMethod($customer)
{
$paymentMethod = PaymentMethod::createNew($this->invitation);
$paymentMethod->ip = Request::ip();
$paymentMethod->account_gateway_token_id = $customer->id;
$paymentMethod->setRelation('account_gateway_token', $customer);
$paymentMethod = $this->creatingPaymentMethod($paymentMethod);
if ($paymentMethod) {
$paymentMethod->save();
}
return $paymentMethod;
}
protected function creatingPaymentMethod($paymentMethod)
{
return $paymentMethod;
}
public function deleteToken()
{
}
public function createPayment($ref = false, $paymentMethod = null)
{
$invitation = $this->invitation;
$invoice = $this->invoice();
$payment = Payment::createNew($invitation);
$payment->invitation_id = $invitation->id;
$payment->account_gateway_id = $this->accountGateway->id;
$payment->invoice_id = $invoice->id;
$payment->amount = $invoice->getRequestedAmount();
$payment->client_id = $invoice->client_id;
$payment->contact_id = $invitation->contact_id;
$payment->transaction_reference = $ref;
$payment->payment_date = date_create()->format('Y-m-d');
$payment->ip = Request::ip();
$payment = $this->creatingPayment($payment);
if ($paymentMethod) {
$payment->last4 = $paymentMethod->last4;
$payment->expiration = $paymentMethod->expiration;
$payment->routing_number = $paymentMethod->routing_number;
$payment->payment_type_id = $paymentMethod->payment_type_id;
$payment->email = $paymentMethod->email;
$payment->bank_name = $paymentMethod->bank_name;
$payment->payment_method_id = $paymentMethod->id;
}
$payment->save();
// TODO move this code
// enable pro plan for hosted users
if ($invoice->account->account_key == NINJA_ACCOUNT_KEY) {
foreach ($invoice->invoice_items as $invoice_item) {
// Hacky, but invoices don't have meta fields to allow us to store this easily
if (1 == preg_match('/^Plan - (.+) \((.+)\)$/', $invoice_item->product_key, $matches)) {
$plan = strtolower($matches[1]);
$term = strtolower($matches[2]);
} elseif ($invoice_item->product_key == 'Pending Monthly') {
$pending_monthly = true;
}
}
if (!empty($plan)) {
$account = Account::with('users')->find($invoice->client->public_id);
if(
$account->company->plan != $plan
|| DateTime::createFromFormat('Y-m-d', $account->company->plan_expires) >= date_create('-7 days')
) {
// Either this is a different plan, or the subscription expired more than a week ago
// Reset any grandfathering
$account->company->plan_started = date_create()->format('Y-m-d');
}
if (
$account->company->plan == $plan
&& $account->company->plan_term == $term
&& DateTime::createFromFormat('Y-m-d', $account->company->plan_expires) >= date_create()
) {
// This is a renewal; mark it paid as of when this term expires
$account->company->plan_paid = $account->company->plan_expires;
} else {
$account->company->plan_paid = date_create()->format('Y-m-d');
}
$account->company->payment_id = $payment->id;
$account->company->plan = $plan;
$account->company->plan_term = $term;
$account->company->plan_expires = DateTime::createFromFormat('Y-m-d', $account->company->plan_paid)
->modify($term == PLAN_TERM_MONTHLY ? '+1 month' : '+1 year')->format('Y-m-d');
if (!empty($pending_monthly)) {
$account->company->pending_plan = $plan;
$account->company->pending_term = PLAN_TERM_MONTHLY;
} else {
$account->company->pending_plan = null;
$account->company->pending_term = null;
}
$account->company->save();
}
}
return $payment;
}
protected function creatingPayment($payment)
{
return $payment;
}
public function refundPayment($payment, $amount)
{
$amount = min($amount, $payment->getCompletedAmount());
if ( ! $amount) {
return false;
}
if ($payment->payment_type_id == PAYMENT_TYPE_CREDIT) {
return $payment->recordRefund($amount);
}
$details = $this->refundDetails($payment, $amount);
$response = $this->gateway()->refund($details)->send();
if ($response->isSuccessful()) {
return $payment->recordRefund($amount);
} elseif ($this->attemptVoidPayment($response, $payment, $amount)) {
$details = ['transactionReference' => $payment->transaction_reference];
$response = $this->gateway->void($details)->send();
if ($response->isSuccessful()) {
return $payment->markVoided();
}
}
return false;
}
protected function refundDetails($payment, $amount)
{
return [
'amount' => $amount,
'transactionReference' => $payment->transaction_reference,
];
}
protected function attemptVoidPayment($response, $payment, $amount)
{
// Partial refund not allowed for unsettled transactions
return $amount == $payment->amount;
}
protected function createLocalPayment($payment)
{
return $payment;
}
public function completeOffsitePurchase($input)
{
$this->input = $input;
$ref = array_get($this->input, 'token') ?: $this->invitation->transaction_reference;
if (method_exists($this->gateway(), 'completePurchase')) {
$details = $this->paymentDetails();
$response = $this->gateway()->completePurchase($details)->send();
$ref = $response->getTransactionReference() ?: $ref;
if ($response->isCancelled()) {
return false;
} elseif ( ! $response->isSuccessful()) {
throw new Exception($response->getMessage());
}
}
return $this->createPayment($ref);
}
public function tokenLinks()
{
if ( ! $this->customer()) {
return [];
}
$paymentMethods = $this->customer()->payment_methods;
$links = [];
foreach ($paymentMethods as $paymentMethod) {
if ($paymentMethod->payment_type_id == PAYMENT_TYPE_ACH && $paymentMethod->status != PAYMENT_METHOD_STATUS_VERIFIED) {
continue;
}
$url = URL::to("/payment/{$this->invitation->invitation_key}/token/".$paymentMethod->public_id);
if ($paymentMethod->payment_type_id == PAYMENT_TYPE_ACH) {
if ($paymentMethod->bank_name) {
$label = $paymentMethod->bank_name;
} else {
$label = trans('texts.use_bank_on_file');
}
} elseif ($paymentMethod->payment_type_id == PAYMENT_TYPE_PAYPAL) {
$label = 'PayPal: ' . $paymentMethod->email;
} else {
$label = trans('texts.use_card_on_file');
}
$links[] = [
'url' => $url,
'label' => $label,
];
}
return $links;
}
public function paymentLinks()
{
$links = [];
foreach ($this->gatewayTypes() as $gatewayType) {
if ($gatewayType === GATEWAY_TYPE_TOKEN) {
continue;
}
$links[] = [
'url' => $this->paymentUrl($gatewayType),
'label' => trans("texts.{$gatewayType}")
];
}
return $links;
}
protected function paymentUrl($gatewayType)
{
$account = $this->account();
$url = URL::to("/payment/{$this->invitation->invitation_key}/{$gatewayType}");
// PayPal doesn't allow being run in an iframe so we need to open in new tab
if ($gatewayType === GATEWAY_TYPE_PAYPAL) {
$url .= "#braintree_paypal";
if ($account->iframe_url) {
return 'javascript:window.open("' . $url . '", "_blank")';
}
}
return $url;
}
protected function parseCardType($cardName) {
$cardTypes = array(
'visa' => PAYMENT_TYPE_VISA,
'americanexpress' => PAYMENT_TYPE_AMERICAN_EXPRESS,
'amex' => PAYMENT_TYPE_AMERICAN_EXPRESS,
'mastercard' => PAYMENT_TYPE_MASTERCARD,
'discover' => PAYMENT_TYPE_DISCOVER,
'jcb' => PAYMENT_TYPE_JCB,
'dinersclub' => PAYMENT_TYPE_DINERS,
'carteblanche' => PAYMENT_TYPE_CARTE_BLANCHE,
'chinaunionpay' => PAYMENT_TYPE_UNIONPAY,
'unionpay' => PAYMENT_TYPE_UNIONPAY,
'laser' => PAYMENT_TYPE_LASER,
'maestro' => PAYMENT_TYPE_MAESTRO,
'solo' => PAYMENT_TYPE_SOLO,
'switch' => PAYMENT_TYPE_SWITCH
);
$cardName = strtolower(str_replace(array(' ', '-', '_'), '', $cardName));
if (empty($cardTypes[$cardName]) && 1 == preg_match('/^('.implode('|', array_keys($cardTypes)).')/', $cardName, $matches)) {
// Some gateways return extra stuff after the card name
$cardName = $matches[1];
}
if (!empty($cardTypes[$cardName])) {
return $cardTypes[$cardName];
} else {
return PAYMENT_TYPE_CREDIT_CARD_OTHER;
}
}
}

View File

@ -0,0 +1,12 @@
<?php namespace App\Ninja\PaymentDrivers;
class BitPayPaymentDriver extends BasePaymentDriver
{
protected function gatewayTypes()
{
return [
GATEWAY_TYPE_BITCOIN
];
}
}

View File

@ -0,0 +1,193 @@
<?php namespace App\Ninja\PaymentDrivers;
use Exception;
use Session;
use Braintree\Customer;
class BraintreePaymentDriver extends BasePaymentDriver
{
protected $customerReferenceParam = 'customerId';
protected $sourceReferenceParam = 'paymentMethodToken';
protected function gatewayTypes()
{
$types = [
GATEWAY_TYPE_CREDIT_CARD,
GATEWAY_TYPE_TOKEN,
];
if ($this->accountGateway->getPayPalEnabled()) {
$types[] = GATEWAY_TYPE_PAYPAL;
}
return $types;
}
public function tokenize()
{
return true;
}
public function startPurchase($input = false, $sourceId = false)
{
$data = parent::startPurchase($input, $sourceId);
if ($this->isGatewayType(GATEWAY_TYPE_PAYPAL)) {
/*
if ( ! $sourceId || empty($input['device_data'])) {
throw new Exception();
}
Session::put($this->invitation->id . 'device_data', $input['device_data']);
*/
$data['details'] = ! empty($input['device_data']) ? json_decode($input['device_data']) : false;
}
return $data;
}
protected function checkCustomerExists($customer)
{
if ( ! parent::checkCustomerExists($customer)) {
return false;
}
$customer = $this->gateway()->findCustomer($customer->token)
->send()
->getData();
return ($customer instanceof Customer);
}
protected function paymentDetails($paymentMethod = false)
{
$data = parent::paymentDetails($paymentMethod);
$deviceData = array_get($this->input, 'device_data') ?: Session::get($this->invitation->id . 'device_data');
if ($deviceData) {
$data['device_data'] = $deviceData;
}
if ($this->isGatewayType(GATEWAY_TYPE_PAYPAL)) {
$data['ButtonSource'] = 'InvoiceNinja_SP';
}
if ( ! empty($this->input['sourceToken'])) {
$data['token'] = $this->input['sourceToken'];
}
return $data;
}
public function createToken()
{
if ($customer = $this->customer()) {
$customerReference = $customer->token;
} else {
$data = $this->paymentDetails();
$tokenResponse = $this->gateway()->createCustomer(['customerData' => $this->customerData()])->send();
if ($tokenResponse->isSuccessful()) {
$customerReference = $tokenResponse->getCustomerData()->id;
} else {
return false;
}
}
if ($customerReference) {
$data['customerId'] = $customerReference;
if ($this->isGatewayType(GATEWAY_TYPE_PAYPAL)) {
$data['paymentMethodNonce'] = $this->input['sourceToken'];
}
$tokenResponse = $this->gateway->createPaymentMethod($data)->send();
if ($tokenResponse->isSuccessful()) {
$this->tokenResponse = $tokenResponse->getData()->paymentMethod;
} else {
return false;
}
}
return parent::createToken();
}
private function customerData()
{
return [
'firstName' => array_get($this->input, 'first_name') ?: $this->contact()->first_name,
'lastName' => array_get($this->input, 'last_name') ?: $this->contact()->last_name,
'company' => $this->client()->name,
'email' => $this->contact()->email,
'phone' => $this->contact()->phone,
'website' => $this->client()->website
];
}
public function creatingCustomer($customer)
{
$customer->token = $this->tokenResponse->customerId;
return $customer;
}
protected function creatingPaymentMethod($paymentMethod)
{
$response = $this->tokenResponse;
$paymentMethod->source_reference = $response->token;
if ($this->isGatewayType(GATEWAY_TYPE_CREDIT_CARD)) {
$paymentMethod->payment_type_id = $this->parseCardType($response->cardType);
$paymentMethod->last4 = $response->last4;
$paymentMethod->expiration = $response->expirationYear . '-' . $response->expirationMonth . '-01';
} elseif ($this->isGatewayType(GATEWAY_TYPE_PAYPAL)) {
$paymentMethod->email = $response->email;
$paymentMethod->payment_type_id = PAYMENT_TYPE_PAYPAL;
} else {
return null;
}
return $paymentMethod;
}
public function removePaymentMethod($paymentMethod)
{
$response = $this->gateway()->deletePaymentMethod([
'token' => $paymentMethod->source_reference
])->send();
if ($response->isSuccessful()) {
return parent::removePaymentMethod($paymentMethod);
} else {
throw new Exception($response->getMessage());
}
}
protected function attemptVoidPayment($response, $payment, $amount)
{
if ( ! parent::attemptVoidPayment($response, $payment, $amount)) {
return false;
}
$data = $response->getData();
if ($data instanceof \Braintree\Result\Error) {
$error = $data->errors->deepAll()[0];
if ($error && $error->code == 91506) {
return true;
}
}
return false;
}
public function createTransactionToken()
{
return $this->gateway()
->clientToken()
->send()
->getToken();
}
}

View File

@ -0,0 +1,35 @@
<?php namespace App\Ninja\PaymentDrivers;
class CheckoutComPaymentDriver extends BasePaymentDriver
{
public function createTransactionToken()
{
$response = $this->gateway()->purchase([
'amount' => $this->invoice()->getRequestedAmount(),
'currency' => $this->client()->getCurrencyCode()
])->send();
if ($response->isRedirect()) {
$token = $response->getTransactionReference();
$this->invitation->transaction_reference = $token;
$this->invitation->save();
return $token;
}
return false;
}
protected function paymentDetails($paymentMethod = false)
{
$data = parent::paymentDetails();
if ($ref = array_get($this->input, 'token')) {
$data['transactionReference'] = $ref;
}
return $data;
}
}

View File

@ -0,0 +1,15 @@
<?php namespace App\Ninja\PaymentDrivers;
class CybersourcePaymentDriver extends BasePaymentDriver
{
protected $transactionReferenceParam = 'transaction_uuid';
public function completeOffsitePurchase($input)
{
if ($input['decision'] == 'ACCEPT') {
return $this->createPayment($input['bill_trans_ref_no']);
} else {
throw new Exception($input['message'] . ': ' . $input['invalid_fields']);
}
}
}

View File

@ -0,0 +1,24 @@
<?php namespace App\Ninja\PaymentDrivers;
class DwollaPaymentDriver extends BasePaymentDriver
{
protected function gatewayTypes()
{
return [GATEWAY_TYPE_DWOLLA];
}
protected function gateway()
{
$gateway = parent::gateway();
if ($gateway->getSandbox() && isset($_ENV['DWOLLA_SANDBOX_KEY']) && isset($_ENV['DWOLLA_SANSBOX_SECRET'])) {
$gateway->setKey($_ENV['DWOLLA_SANDBOX_KEY']);
$gateway->setSecret($_ENV['DWOLLA_SANSBOX_SECRET']);
} elseif (isset($_ENV['DWOLLA_KEY']) && isset($_ENV['DWOLLA_SECRET'])) {
$gateway->setKey($_ENV['DWOLLA_KEY']);
$gateway->setSecret($_ENV['DWOLLA_SECRET']);
}
return $gateway;
}
}

View File

@ -0,0 +1,6 @@
<?php namespace App\Ninja\PaymentDrivers;
class EwayRapidSharedPaymentDriver extends BasePaymentDriver
{
protected $transactionReferenceParam = 'AccessCode';
}

View File

@ -0,0 +1,6 @@
<?php namespace App\Ninja\PaymentDrivers;
class GoCardlessPaymentDriver extends BasePaymentDriver
{
protected $transactionReferenceParam = 'signature';
}

View File

@ -0,0 +1,16 @@
<?php namespace App\Ninja\PaymentDrivers;
class MolliePaymentDriver extends BasePaymentDriver
{
public function completeOffsitePurchase($input)
{
$details = $this->paymentDetails();
$details['transactionReference'] = $this->invitation->transaction_reference;
$response = $this->gateway()->fetchTransaction($details)->send();
return $this->createPayment($response->getTransactionReference());
}
}

View File

@ -0,0 +1,13 @@
<?php namespace App\Ninja\PaymentDrivers;
class PayFastPaymentDriver extends BasePaymentDriver
{
protected $transactionReferenceParam = 'm_payment_id';
public function completeOffsitePurchase($input)
{
if ($accountGateway->isGateway(GATEWAY_PAYFAST) && Request::has('pt')) {
$token = Request::query('pt');
}
}
}

View File

@ -0,0 +1,29 @@
<?php namespace App\Ninja\PaymentDrivers;
use Exception;
class PayPalExpressPaymentDriver extends BasePaymentDriver
{
protected function gatewayTypes()
{
return [
GATEWAY_TYPE_PAYPAL
];
}
protected function paymentDetails($paymentMethod = false)
{
$data = parent::paymentDetails();
$data['ButtonSource'] = 'InvoiceNinja_SP';
return $data;
}
protected function creatingPayment($payment)
{
$payment->payer_id = $this->input['PayerID'];
return $payment;
}
}

View File

@ -0,0 +1,20 @@
<?php namespace App\Ninja\PaymentDrivers;
class PayPalProPaymentDriver extends BasePaymentDriver
{
protected function gatewayTypes()
{
return [
GATEWAY_TYPE_CREDIT_CARD
];
}
protected function paymentDetails($paymentMethod = false)
{
$data = parent::paymentDetails();
$data['ButtonSource'] = 'InvoiceNinja_SP';
return $data;
}
}

View File

@ -0,0 +1,304 @@
<?php namespace App\Ninja\PaymentDrivers;
use Exception;
use Cache;
use App\Models\PaymentMethod;
class StripePaymentDriver extends BasePaymentDriver
{
protected $customerReferenceParam = 'customerReference';
protected function gatewayTypes()
{
$types = [
GATEWAY_TYPE_CREDIT_CARD,
GATEWAY_TYPE_TOKEN
];
if ($this->accountGateway->getAchEnabled()) {
$types[] = GATEWAY_TYPE_BANK_TRANSFER;
}
return $types;
}
public function tokenize()
{
return $this->accountGateway->getPublishableStripeKey();
}
public function rules()
{
$rules = parent::rules();
if ($this->isGatewayType(GATEWAY_TYPE_BANK_TRANSFER)) {
$rules['authorize_ach'] = 'required';
}
return $rules;
}
protected function checkCustomerExists($customer)
{
$response = $this->gateway()
->fetchCustomer(['customerReference' => $customer->token])
->send();
if ( ! $response->isSuccessful()) {
return false;
}
$this->tokenResponse = $response->getData();
// import Stripe tokens created before payment methods table was added
if ( ! count($customer->payment_methods)) {
if ($paymentMethod = $this->createPaymentMethod($customer)) {
$customer->default_payment_method_id = $paymentMethod->id;
$customer->save();
$customer->load('payment_methods');
}
}
return true;
}
public function isTwoStep()
{
return $this->isGatewayType(GATEWAY_TYPE_BANK_TRANSFER) && empty($this->input['plaidPublicToken']);
}
protected function paymentDetails($paymentMethod = false)
{
$data = parent::paymentDetails($paymentMethod);
if ( ! empty($this->input['sourceToken'])) {
$data['token'] = $this->input['sourceToken'];
unset($data['card']);
}
if ( ! empty($this->input['plaidPublicToken'])) {
$data['plaidPublicToken'] = $this->input['plaidPublicToken'];
$data['plaidAccountId'] = $this->input['plaidAccountId'];
unset($data['card']);
}
return $data;
}
public function createToken()
{
$invoice = $this->invitation->invoice;
$client = $invoice->client;
$data = $this->paymentDetails();
$data['description'] = $client->getDisplayName();
if ( ! empty($data['plaidPublicToken'])) {
$plaidResult = $this->getPlaidToken($data['plaidPublicToken'], $data['plaidAccountId']);
unset($data['plaidPublicToken']);
unset($data['plaidAccountId']);
$data['token'] = $plaidResult['stripe_bank_account_token'];
}
// if a customer already exists link the token to it
if ($customer = $this->customer()) {
$data['customerReference'] = $customer->token;
}
$tokenResponse = $this->gateway()
->createCard($data)
->send();
if ($tokenResponse->isSuccessful()) {
$this->tokenResponse = $tokenResponse->getData();
return parent::createToken();
} else {
throw new Exception($tokenResponse->getMessage());
}
}
public function creatingCustomer($customer)
{
$customer->token = $this->tokenResponse['id'];
return $customer;
}
protected function creatingPaymentMethod($paymentMethod)
{
$data = $this->tokenResponse;
if (!empty($data['object']) && ($data['object'] == 'card' || $data['object'] == 'bank_account')) {
$source = $data;
} elseif (!empty($data['object']) && $data['object'] == 'customer') {
$sources = !empty($data['sources']) ? $data['sources'] : $data['cards'];
$source = reset($sources['data']);
} else {
$source = !empty($data['source']) ? $data['source'] : $data['card'];
}
if ( ! $source) {
return false;
}
$paymentMethod->source_reference = $source['id'];
$paymentMethod->last4 = $source['last4'];
if ($this->isGatewayType(GATEWAY_TYPE_CREDIT_CARD)) {
$paymentMethod->expiration = $source['exp_year'] . '-' . $source['exp_month'] . '-01';
$paymentMethod->payment_type_id = $this->parseCardType($source['brand']);
} elseif ($this->isGatewayType(GATEWAY_TYPE_BANK_TRANSFER)) {
$paymentMethod->routing_number = $source['routing_number'];
$paymentMethod->payment_type_id = PAYMENT_TYPE_ACH;
$paymentMethod->status = $source['status'];
$currency = Cache::get('currencies')->where('code', strtoupper($source['currency']))->first();
if ($currency) {
$paymentMethod->currency_id = $currency->id;
$paymentMethod->setRelation('currency', $currency);
}
}
return $paymentMethod;
}
protected function creatingPayment($payment)
{
if ($this->isGatewayType(GATEWAY_TYPE_BANK_TRANSFER)) {
$payment->payment_status_id = $this->purchaseResponse['status'] == 'succeeded' ? PAYMENT_STATUS_COMPLETED : PAYMENT_STATUS_PENDING;
}
return $payment;
}
public function removePaymentMethod($paymentMethod)
{
if ( ! $paymentMethod->relationLoaded('account_gateway_token')) {
$paymentMethod->load('account_gateway_token');
}
$response = $this->gateway()->deleteCard([
'customerReference' => $paymentMethod->account_gateway_token->token,
'cardReference' => $paymentMethod->source_reference
])->send();
if ($response->isSuccessful()) {
return parent::removePaymentMethod($paymentMethod);
} else {
throw new Exception($response->getMessage());
}
}
private function getPlaidToken($publicToken, $accountId)
{
$clientId = $this->accountGateway->getPlaidClientId();
$secret = $this->accountGateway->getPlaidSecret();
if (!$clientId) {
throw new Exception('plaid client id not set'); // TODO use text strings
}
if (!$secret) {
throw new Exception('plaid secret not set');
}
try {
$subdomain = $this->accountGateway->getPlaidEnvironment() == 'production' ? 'api' : 'tartan';
$response = (new \GuzzleHttp\Client(['base_uri'=>"https://{$subdomain}.plaid.com"]))->request(
'POST',
'exchange_token',
[
'allow_redirects' => false,
'headers' => ['content-type' => 'application/x-www-form-urlencoded'],
'body' => http_build_query(array(
'client_id' => $clientId,
'secret' => $secret,
'public_token' => $publicToken,
'account_id' => $accountId,
))
]
);
return json_decode($response->getBody(), true);
} catch (\GuzzleHttp\Exception\BadResponseException $e) {
$response = $e->getResponse();
$body = json_decode($response->getBody(), true);
if ($body && !empty($body['message'])) {
throw new Exception($body['message']);
} else {
throw new Exception($e->getMessage());
}
}
}
public function verifyBankAccount($client, $publicId, $amount1, $amount2)
{
$customer = $this->customer($client->id);
$paymentMethod = PaymentMethod::clientId($client->id)
->wherePublicId($publicId)
->firstOrFail();
// Omnipay doesn't support verifying payment methods
// Also, it doesn't want to urlencode without putting numbers inside the brackets
$result = $this->makeStripeCall(
'POST',
'customers/' . $customer->token . '/sources/' . $paymentMethod->source_reference . '/verify',
'amounts[]=' . intval($amount1) . '&amounts[]=' . intval($amount2)
);
if (is_string($result)) {
return $result;
}
$paymentMethod->status = PAYMENT_METHOD_STATUS_VERIFIED;
$paymentMethod->save();
if ( ! $customer->default_payment_method_id) {
$customer->default_payment_method_id = $paymentMethod->id;
$customer->save();
}
return true;
}
public function makeStripeCall($method, $url, $body = null)
{
$apiKey = $this->accountGateway->getConfig()->apiKey;
if (!$apiKey) {
return 'No API key set';
}
try{
$options = [
'headers' => ['content-type' => 'application/x-www-form-urlencoded'],
'auth' => [$apiKey, ''],
];
if ($body) {
$options['body'] = $body;
}
$response = (new \GuzzleHttp\Client(['base_uri'=>'https://api.stripe.com/v1/']))->request(
$method,
$url,
$options
);
return json_decode($response->getBody(), true);
} catch (\GuzzleHttp\Exception\BadResponseException $e) {
$response = $e->getResponse();
$body = json_decode($response->getBody(), true);
if ($body && $body['error'] && $body['error']['type'] == 'invalid_request_error') {
return $body['error']['message'];
}
return $e->getMessage();
}
}
}

View File

@ -0,0 +1,13 @@
<?php namespace App\Ninja\PaymentDrivers;
class TwoCheckoutPaymentDriver extends BasePaymentDriver
{
protected $transactionReferenceParam = 'cart_order_id';
// Calling completePurchase results in an 'invalid key' error
public function completeOffsitePurchase($input)
{
return $this->createPayment($input['order_number']);
}
}

View File

@ -0,0 +1,118 @@
<?php namespace App\Ninja\PaymentDrivers;
class WePayPaymentDriver extends BasePaymentDriver
{
protected function gatewayTypes()
{
return [
GATEWAY_TYPE_CREDIT_CARD,
GATEWAY_TYPE_BANK_TRANSFER,
GATEWAY_TYPE_TOKEN
];
}
public function startPurchase($input, $sourceId)
{
$data = parent::startPurchase($input, $sourceId);
if ($this->isGatewayType(GATEWAY_TYPE_BANK_TRANSFER)) {
if ( ! $sourceId) {
throw new Exception();
}
}
return $data;
}
public function tokenize()
{
return true;
}
protected function checkCustomerExists($customer)
{
return true;
}
public function rules()
{
$rules = parent::rules();
if ($this->isGatewayType(GATEWAY_TYPE_BANK_TRANSFER)) {
$rules = array_merge($rules, [
'authorize_ach' => 'required',
'tos_agree' => 'required',
]);
}
return $rules;
}
protected function paymentDetails($paymentMethod = false)
{
$data = parent::paymentDetails($paymentMethod);
if ($transactionId = Session::get($invitation->id . 'payment_ref')) {
$data['transaction_id'] = $transactionId;
}
$data['applicationFee'] = $this->calculateApplicationFee($data['amount']);
$data['feePayer'] = WEPAY_FEE_PAYER;
$data['callbackUri'] = $this->accountGateway->getWebhookUrl();
if ($this->isGatewayType(GATEWAY_TYPE_BANK_TRANSFER)) {
$data['paymentMethodType'] = 'payment_bank';
}
return $data;
}
public function removePaymentMethod($paymentMethod)
{
$wepay = Utils::setupWePay($this->accountGateway);
$wepay->request('/credit_card/delete', [
'client_id' => WEPAY_CLIENT_ID,
'client_secret' => WEPAY_CLIENT_SECRET,
'credit_card_id' => intval($paymentMethod->source_reference),
]);
if ($response->isSuccessful()) {
return parent::removePaymentMethod($paymentMethod);
} else {
throw new Exception($response->getMessage());
}
}
protected function refundDetails($payment, $amount)
{
$data = parent::refundDetails($parent);
$data['refund_reason'] = 'Refund issued by merchant.';
// WePay issues a full refund when no amount is set. If an amount is set, it will try
// to issue a partial refund without refunding any fees. However, the Stripe driver
// (but not the API) requires the amount parameter to be set no matter what.
if ($data['amount'] == $payment->getCompletedAmount()) {
unset($data['amount']);
}
return $data;
}
protected function attemptVoidPayment($response, $payment, $amount)
{
if ( ! parent::attemptVoidPayment($response, $payment, $amount)) {
return false;
}
return $response->getCode() == 4004;
}
private function calculateApplicationFee($amount)
{
$fee = WEPAY_APP_FEE_MULTIPLIER * $amount + WEPAY_APP_FEE_FIXED;
return floor(min($fee, $amount * 0.2));// Maximum fee is 20% of the amount.
}
}

View File

@ -741,6 +741,7 @@ class InvoiceRepository extends BaseRepository
}
$invoice = Invoice::createNew($recurInvoice);
$invoice->invoice_type_id = INVOICE_TYPE_STANDARD;
$invoice->client_id = $recurInvoice->client_id;
$invoice->recurring_invoice_id = $recurInvoice->id;
$invoice->invoice_number = $recurInvoice->account->getNextInvoiceNumber($invoice);

View File

@ -114,11 +114,9 @@ class DatatableService
. trans("texts.archive_{$datatable->entityType}") . "</a></li>";
}
} else if($can_edit) {
if ($datatable->entityType != ENTITY_ACCOUNT_GATEWAY || Auth::user()->account->canAddGateway(\App\Models\Gateway::getPaymentType($model->gateway_id))) {
$dropdown_contents .= "<li><a href=\"javascript:restoreEntity({$model->public_id})\">"
. trans("texts.restore_{$datatable->entityType}") . "</a></li>";
}
}
if (property_exists($model, 'is_deleted') && !$model->is_deleted && $can_edit) {
$dropdown_contents .= "<li><a href=\"javascript:deleteEntity({$model->public_id})\">"

View File

@ -35,14 +35,19 @@ class InvoiceService extends BaseService
{
if (isset($data['client'])) {
$canSaveClient = false;
$canViewClient = false;
$clientPublicId = array_get($data, 'client.public_id') ?: array_get($data, 'client.id');
if (empty($clientPublicId) || $clientPublicId == '-1') {
$canSaveClient = Auth::user()->can('create', ENTITY_CLIENT);
} else {
$canSaveClient = Auth::user()->can('edit', Client::scope($clientPublicId)->first());
$client = Client::scope($clientPublicId)->first();
$canSaveClient = Auth::user()->can('edit', $client);
$canViewClient = Auth::user()->can('view', $client);
}
if ($canSaveClient) {
$client = $this->clientRepo->save($data['client']);
}
if ($canSaveClient || $canViewClient) {
$data['client_id'] = $client->id;
}
}

View File

@ -43,360 +43,9 @@ class PaymentService extends BaseService
return $this->paymentRepo;
}
public function createGateway($accountGateway)
{
$gateway = Omnipay::create($accountGateway->gateway->provider);
$gateway->initialize((array)$accountGateway->getConfig());
if ($accountGateway->isGateway(GATEWAY_DWOLLA)) {
if ($gateway->getSandbox() && isset($_ENV['DWOLLA_SANDBOX_KEY']) && isset($_ENV['DWOLLA_SANSBOX_SECRET'])) {
$gateway->setKey($_ENV['DWOLLA_SANDBOX_KEY']);
$gateway->setSecret($_ENV['DWOLLA_SANSBOX_SECRET']);
} elseif (isset($_ENV['DWOLLA_KEY']) && isset($_ENV['DWOLLA_SECRET'])) {
$gateway->setKey($_ENV['DWOLLA_KEY']);
$gateway->setSecret($_ENV['DWOLLA_SECRET']);
}
}
return $gateway;
}
public function getPaymentDetails($invitation, $accountGateway, $input = null)
{
$invoice = $invitation->invoice;
$account = $invoice->account;
$key = $invoice->account_id . '-' . $invoice->invoice_number;
$currencyCode = $invoice->client->currency ? $invoice->client->currency->code : ($invoice->account->currency ? $invoice->account->currency->code : 'USD');
if ($input) {
$data = self::convertInputForOmnipay($input);
Session::put($key, $data);
} elseif (Session::get($key)) {
$data = Session::get($key);
} else {
$data = $this->createDataForClient($invitation);
}
$card = !empty($data['number']) ? new CreditCard($data) : null;
$data = [
'amount' => $invoice->getRequestedAmount(),
'card' => $card,
'currency' => $currencyCode,
'returnUrl' => URL::to('complete'),
'cancelUrl' => $invitation->getLink(),
'description' => trans('texts.' . $invoice->getEntityType()) . " {$invoice->invoice_number}",
'transactionId' => $invoice->invoice_number,
'transactionType' => 'Purchase',
];
if ($input !== null) {
$data['ip'] = \Request::ip();
}
if ($accountGateway->isGateway(GATEWAY_PAYPAL_EXPRESS) || $accountGateway->isGateway(GATEWAY_PAYPAL_PRO)) {
$data['ButtonSource'] = 'InvoiceNinja_SP';
};
if ($input) {
if (!empty($input['sourceToken'])) {
$data['token'] = $input['sourceToken'];
unset($data['card']);
} elseif (!empty($input['plaidPublicToken'])) {
$data['plaidPublicToken'] = $input['plaidPublicToken'];
$data['plaidAccountId'] = $input['plaidAccountId'];
unset($data['card']);
}
}
if ($accountGateway->isGateway(GATEWAY_WEPAY) && $transactionId = Session::get($invitation->id.'payment_ref')) {
$data['transaction_id'] = $transactionId;
}
return $data;
}
public function convertInputForOmnipay($input)
{
$data = [
'firstName' => isset($input['first_name']) ? $input['first_name'] : null,
'lastName' =>isset($input['last_name']) ? $input['last_name'] : null,
'email' => isset($input['email']) ? $input['email'] : null,
'number' => isset($input['card_number']) ? $input['card_number'] : null,
'expiryMonth' => isset($input['expiration_month']) ? $input['expiration_month'] : null,
'expiryYear' => isset($input['expiration_year']) ? $input['expiration_year'] : null,
];
// allow space until there's a setting to disable
if (isset($input['cvv']) && $input['cvv'] != ' ') {
$data['cvv'] = $input['cvv'];
}
if (isset($input['address1'])) {
$country = Country::find($input['country_id']);
$data = array_merge($data, [
'billingAddress1' => $input['address1'],
'billingAddress2' => $input['address2'],
'billingCity' => $input['city'],
'billingState' => $input['state'],
'billingPostcode' => $input['postal_code'],
'billingCountry' => $country->iso_3166_2,
'shippingAddress1' => $input['address1'],
'shippingAddress2' => $input['address2'],
'shippingCity' => $input['city'],
'shippingState' => $input['state'],
'shippingPostcode' => $input['postal_code'],
'shippingCountry' => $country->iso_3166_2
]);
}
return $data;
}
public function createDataForClient($invitation)
{
$invoice = $invitation->invoice;
$client = $invoice->client;
$contact = $invitation->contact ?: $client->contacts()->first();
return [
'email' => $contact->email,
'company' => $client->getDisplayName(),
'firstName' => $contact->first_name,
'lastName' => $contact->last_name,
'billingAddress1' => $client->address1,
'billingAddress2' => $client->address2,
'billingCity' => $client->city,
'billingPostcode' => $client->postal_code,
'billingState' => $client->state,
'billingCountry' => $client->country ? $client->country->iso_3166_2 : '',
'billingPhone' => $contact->phone,
'shippingAddress1' => $client->address1,
'shippingAddress2' => $client->address2,
'shippingCity' => $client->city,
'shippingPostcode' => $client->postal_code,
'shippingState' => $client->state,
'shippingCountry' => $client->country ? $client->country->iso_3166_2 : '',
'shippingPhone' => $contact->phone,
];
}
public function getClientPaymentMethods($client)
{
$token = $client->getGatewayToken($accountGateway/* return parameter */, $accountGatewayToken/* return parameter */);
if (!$token) {
return null;
}
if (!$accountGatewayToken->uses_local_payment_methods && $accountGateway->gateway_id == GATEWAY_STRIPE) {
// Migrate Stripe data
$gateway = $this->createGateway($accountGateway);
$response = $gateway->fetchCustomer(array('customerReference' => $token))->send();
if (!$response->isSuccessful()) {
return null;
}
$data = $response->getData();
$sources_list = isset($data['sources']) ? $data['sources'] : $data['cards'];
$sources = isset($sources_list['data'])?$sources_list['data']:$sources_list;
// Load
$accountGatewayToken->payment_methods();
foreach ($sources as $source) {
$paymentMethod = $this->convertPaymentMethodFromStripe($source, $accountGatewayToken);
if ($paymentMethod) {
$paymentMethod->save();
}
if ($data['default_source'] == $source['id']) {
$accountGatewayToken->default_payment_method_id = $paymentMethod->id;
}
}
$accountGatewayToken->uses_local_payment_methods = true;
$accountGatewayToken->save();
}
return $accountGatewayToken->payment_methods;
}
public function verifyClientPaymentMethod($client, $publicId, $amount1, $amount2)
{
$token = $client->getGatewayToken($accountGateway/* return parameter */, $accountGatewayToken/* return parameter */);
if ($accountGateway->gateway_id != GATEWAY_STRIPE) {
return 'Unsupported gateway';
}
$paymentMethod = PaymentMethod::scope($publicId, $client->account_id, $accountGatewayToken->id)->firstOrFail();
// Omnipay doesn't support verifying payment methods
// Also, it doesn't want to urlencode without putting numbers inside the brackets
$result = $this->makeStripeCall(
$accountGateway,
'POST',
'customers/' . $token . '/sources/' . $paymentMethod->source_reference . '/verify',
'amounts[]=' . intval($amount1) . '&amounts[]=' . intval($amount2)
);
if (is_string($result)) {
return $result;
}
$paymentMethod->status = PAYMENT_METHOD_STATUS_VERIFIED;
$paymentMethod->save();
if (!$paymentMethod->account_gateway_token->default_payment_method_id) {
$paymentMethod->account_gateway_token->default_payment_method_id = $paymentMethod->id;
$paymentMethod->account_gateway_token->save();
}
return true;
}
public function removeClientPaymentMethod($client, $publicId)
{
$token = $client->getGatewayToken($accountGateway/* return parameter */, $accountGatewayToken/* return parameter */);
if (!$token) {
return null;
}
$paymentMethod = PaymentMethod::scope($publicId, $client->account_id, $accountGatewayToken->id)->firstOrFail();
$gateway = $this->createGateway($accountGateway);
if ($accountGateway->gateway_id == GATEWAY_STRIPE) {
$response = $gateway->deleteCard(array('customerReference' => $token, 'cardReference' => $paymentMethod->source_reference))->send();
if (!$response->isSuccessful()) {
return $response->getMessage();
}
} elseif ($accountGateway->gateway_id == GATEWAY_BRAINTREE) {
$response = $gateway->deletePaymentMethod(array('token' => $paymentMethod->source_reference))->send();
if (!$response->isSuccessful()) {
return $response->getMessage();
}
} elseif ($accountGateway->gateway_id == GATEWAY_WEPAY) {
try {
$wepay = Utils::setupWePay($accountGateway);
$wepay->request('/credit_card/delete', [
'client_id' => WEPAY_CLIENT_ID,
'client_secret' => WEPAY_CLIENT_SECRET,
'credit_card_id' => intval($paymentMethod->source_reference),
]);
} catch (\WePayException $ex){
return $ex->getMessage();
}
}
$paymentMethod->delete();
return true;
}
public function setClientDefaultPaymentMethod($client, $publicId)
{
$token = $client->getGatewayToken($accountGateway/* return parameter */, $accountGatewayToken/* return parameter */);
if (!$token) {
return null;
}
$paymentMethod = PaymentMethod::scope($publicId, $client->account_id, $accountGatewayToken->id)->firstOrFail();
$paymentMethod->account_gateway_token->default_payment_method_id = $paymentMethod->id;
$paymentMethod->account_gateway_token->save();
return true;
}
public function createToken($paymentType, $gateway, $details, $accountGateway, $client, $contactId, &$customerReference = null, &$paymentMethod = null)
{
$customerReference = $client->getGatewayToken($accountGateway, $accountGatewayToken/* return paramenter */);
if ($customerReference && $customerReference != CUSTOMER_REFERENCE_LOCAL) {
$details['customerReference'] = $customerReference;
if ($accountGateway->gateway_id == GATEWAY_STRIPE) {
$customerResponse = $gateway->fetchCustomer(array('customerReference' => $customerReference))->send();
if (!$customerResponse->isSuccessful()) {
$customerReference = null; // The customer might not exist anymore
}
} elseif ($accountGateway->gateway_id == GATEWAY_BRAINTREE) {
$customer = $gateway->findCustomer($customerReference)->send()->getData();
if (!($customer instanceof \Braintree\Customer)) {
$customerReference = null; // The customer might not exist anymore
}
}
}
if ($accountGateway->gateway_id == GATEWAY_STRIPE) {
if (!empty($details['plaidPublicToken'])) {
$plaidResult = $this->getPlaidToken($accountGateway, $details['plaidPublicToken'], $details['plaidAccountId']);
if (is_string($plaidResult)) {
$this->lastError = $plaidResult;
return;
} elseif (!$plaidResult) {
$this->lastError = 'No token received from Plaid';
return;
}
unset($details['plaidPublicToken']);
unset($details['plaidAccountId']);
$details['token'] = $plaidResult['stripe_bank_account_token'];
}
$tokenResponse = $gateway->createCard($details)->send();
if ($tokenResponse->isSuccessful()) {
$sourceReference = $tokenResponse->getCardReference();
if (!$customerReference) {
$customerReference = $tokenResponse->getCustomerReference();
}
if (!$sourceReference) {
$responseData = $tokenResponse->getData();
if (!empty($responseData['object']) && ($responseData['object'] == 'bank_account' || $responseData['object'] == 'card')) {
$sourceReference = $responseData['id'];
}
}
if ($customerReference == $sourceReference) {
// This customer was just created; find the card
$data = $tokenResponse->getData();
if (!empty($data['default_source'])) {
$sourceReference = $data['default_source'];
}
}
} else {
$data = $tokenResponse->getData();
if ($data && $data['error'] && $data['error']['type'] == 'invalid_request_error') {
$this->lastError = $data['error']['message'];
return;
}
}
} elseif ($accountGateway->gateway_id == GATEWAY_BRAINTREE) {
if (!$customerReference) {
$tokenResponse = $gateway->createCustomer(array('customerData' => array()))->send();
if ($tokenResponse->isSuccessful()) {
$customerReference = $tokenResponse->getCustomerData()->id;
} else {
$this->lastError = $tokenResponse->getData()->message;
return;
}
}
if ($customerReference) {
$details['customerId'] = $customerReference;
$tokenResponse = $gateway->createPaymentMethod($details)->send();
if ($tokenResponse->isSuccessful()) {
$sourceReference = $tokenResponse->getData()->paymentMethod->token;
} else {
$this->lastError = $tokenResponse->getData()->message;
return;
}
}
if ($accountGateway->gateway_id == GATEWAY_BRAINTREE) {
} elseif ($accountGateway->gateway_id == GATEWAY_WEPAY) {
$wepay = Utils::setupWePay($accountGateway);
try {
@ -466,60 +115,6 @@ class PaymentService extends BaseService
return $sourceReference;
}
public function convertPaymentMethodFromStripe($source, $accountGatewayToken = null, $paymentMethod = null) {
// Creating a new one or updating an existing one
if (!$paymentMethod) {
$paymentMethod = $accountGatewayToken ? PaymentMethod::createNew($accountGatewayToken) : new PaymentMethod();
}
$paymentMethod->last4 = $source['last4'];
$paymentMethod->source_reference = $source['id'];
if ($source['object'] == 'bank_account') {
$paymentMethod->routing_number = $source['routing_number'];
$paymentMethod->payment_type_id = PAYMENT_TYPE_ACH;
$paymentMethod->status = $source['status'];
$currency = Cache::get('currencies')->where('code', strtoupper($source['currency']))->first();
if ($currency) {
$paymentMethod->currency_id = $currency->id;
$paymentMethod->setRelation('currency', $currency);
}
} elseif ($source['object'] == 'card') {
$paymentMethod->expiration = $source['exp_year'] . '-' . $source['exp_month'] . '-01';
$paymentMethod->payment_type_id = $this->parseCardType($source['brand']);
} else {
return null;
}
$paymentMethod->setRelation('payment_type', Cache::get('paymentTypes')->find($paymentMethod->payment_type_id));
return $paymentMethod;
}
public function convertPaymentMethodFromBraintree($source, $accountGatewayToken = null, $paymentMethod = null) {
// Creating a new one or updating an existing one
if (!$paymentMethod) {
$paymentMethod = $accountGatewayToken ? PaymentMethod::createNew($accountGatewayToken) : new PaymentMethod();
}
if ($source instanceof \Braintree\CreditCard) {
$paymentMethod->payment_type_id = $this->parseCardType($source->cardType);
$paymentMethod->last4 = $source->last4;
$paymentMethod->expiration = $source->expirationYear . '-' . $source->expirationMonth . '-01';
} elseif ($source instanceof \Braintree\PayPalAccount) {
$paymentMethod->email = $source->email;
$paymentMethod->payment_type_id = PAYMENT_TYPE_ID_PAYPAL;
} else {
return null;
}
$paymentMethod->setRelation('payment_type', Cache::get('paymentTypes')->find($paymentMethod->payment_type_id));
$paymentMethod->source_reference = $source->token;
return $paymentMethod;
}
public function convertPaymentMethodFromWePay($source, $accountGatewayToken = null, $paymentMethod = null) {
// Creating a new one or updating an existing one
if (!$paymentMethod) {
@ -554,47 +149,7 @@ class PaymentService extends BaseService
}
public function convertPaymentMethodFromGatewayResponse($gatewayResponse, $accountGateway, $accountGatewayToken = null, $contactId = null, $existingPaymentMethod = null) {
if ($accountGateway->gateway_id == GATEWAY_STRIPE) {
$data = $gatewayResponse->getData();
if (!empty($data['object']) && ($data['object'] == 'card' || $data['object'] == 'bank_account')) {
$source = $data;
} elseif (!empty($data['object']) && $data['object'] == 'customer') {
$sources = !empty($data['sources']) ? $data['sources'] : $data['cards'];
$source = reset($sources['data']);
} else {
$source = !empty($data['source']) ? $data['source'] : $data['card'];
}
if ($source) {
$paymentMethod = $this->convertPaymentMethodFromStripe($source, $accountGatewayToken, $existingPaymentMethod);
}
} elseif ($accountGateway->gateway_id == GATEWAY_BRAINTREE) {
$data = $gatewayResponse->getData();
if (!empty($data->transaction)) {
$transaction = $data->transaction;
if ($existingPaymentMethod) {
$paymentMethod = $existingPaymentMethod;
} else {
$paymentMethod = $accountGatewayToken ? PaymentMethod::createNew($accountGatewayToken) : new PaymentMethod();
}
if ($transaction->paymentInstrumentType == 'credit_card') {
$card = $transaction->creditCardDetails;
$paymentMethod->last4 = $card->last4;
$paymentMethod->expiration = $card->expirationYear . '-' . $card->expirationMonth . '-01';
$paymentMethod->payment_type_id = $this->parseCardType($card->cardType);
} elseif ($transaction->paymentInstrumentType == 'paypal_account') {
$paymentMethod->payment_type_id = PAYMENT_TYPE_ID_PAYPAL;
$paymentMethod->email = $transaction->paypalDetails->payerEmail;
}
$paymentMethod->setRelation('payment_type', Cache::get('paymentTypes')->find($paymentMethod->payment_type_id));
} elseif (!empty($data->paymentMethod)) {
$paymentMethod = $this->convertPaymentMethodFromBraintree($data->paymentMethod, $accountGatewayToken, $existingPaymentMethod);
}
} elseif ($accountGateway->gateway_id == GATEWAY_WEPAY) {
if ($accountGateway->gateway_id == GATEWAY_WEPAY) {
if ($gatewayResponse instanceof \Omnipay\WePay\Message\CustomCheckoutResponse) {
$wepay = \Utils::setupWePay($accountGateway);
$paymentMethodType = $gatewayResponse->getData()['payment_method']['type'];
@ -624,255 +179,27 @@ class PaymentService extends BaseService
return $paymentMethod;
}
public function getCheckoutComToken($invitation)
{
$token = false;
$invoice = $invitation->invoice;
$client = $invoice->client;
$account = $invoice->account;
$accountGateway = $account->getGatewayConfig(GATEWAY_CHECKOUT_COM);
$response = $this->purchase($accountGateway, [
'amount' => $invoice->getRequestedAmount(),
'currency' => $client->currency ? $client->currency->code : ($account->currency ? $account->currency->code : 'USD')
])->send();
if ($response->isRedirect()) {
$token = $response->getTransactionReference();
}
Session::set($invitation->id . 'payment_type', PAYMENT_TYPE_CREDIT_CARD);
return $token;
}
public function getBraintreeClientToken($account)
{
$token = false;
$accountGateway = $account->getGatewayConfig(GATEWAY_BRAINTREE);
$gateway = $this->createGateway($accountGateway);
$token = $gateway->clientToken()->send()->getToken();
return $token;
}
public function createPayment($invitation, $accountGateway, $ref, $payerId = null, $paymentDetails = null, $paymentMethod = null, $purchaseResponse = null)
{
$invoice = $invitation->invoice;
$payment = Payment::createNew($invitation);
$payment->invitation_id = $invitation->id;
$payment->account_gateway_id = $accountGateway->id;
$payment->invoice_id = $invoice->id;
$payment->amount = $invoice->getRequestedAmount();
$payment->client_id = $invoice->client_id;
$payment->contact_id = $invitation->contact_id;
$payment->transaction_reference = $ref;
$payment->payment_date = date_create()->format('Y-m-d');
if (!empty($paymentDetails['card'])) {
$card = $paymentDetails['card'];
$payment->last4 = $card->getNumberLastFour();
$payment->payment_type_id = $this->detectCardType($card->getNumber());
}
if (!empty($paymentDetails['ip'])) {
$payment->ip = $paymentDetails['ip'];
}
$savePaymentMethod = !empty($paymentMethod);
// This will convert various gateway's formats to a known format
$paymentMethod = $this->convertPaymentMethodFromGatewayResponse($purchaseResponse, $accountGateway, null, null, $paymentMethod);
// If this is a stored payment method, we'll update it with the latest info
if ($savePaymentMethod) {
$paymentMethod->save();
}
if ($accountGateway->gateway_id == GATEWAY_STRIPE) {
$data = $purchaseResponse->getData();
$payment->payment_status_id = $data['status'] == 'succeeded' ? PAYMENT_STATUS_COMPLETED : PAYMENT_STATUS_PENDING;
}
if ($paymentMethod) {
if ($paymentMethod->last4) {
$payment->last4 = $paymentMethod->last4;
}
if ($paymentMethod->expiration) {
$payment->expiration = $paymentMethod->expiration;
}
if ($paymentMethod->routing_number) {
$payment->routing_number = $paymentMethod->routing_number;
}
if ($paymentMethod->payment_type_id) {
$payment->payment_type_id = $paymentMethod->payment_type_id;
}
if ($paymentMethod->email) {
$payment->email = $paymentMethod->email;
}
if ($paymentMethod->bank_name) {
$payment->bank_name = $paymentMethod->bank_name;
}
if ($payerId) {
$payment->payer_id = $payerId;
}
if ($savePaymentMethod) {
$payment->payment_method_id = $paymentMethod->id;
}
}
$payment->save();
// enable pro plan for hosted users
if ($invoice->account->account_key == NINJA_ACCOUNT_KEY) {
foreach ($invoice->invoice_items as $invoice_item) {
// Hacky, but invoices don't have meta fields to allow us to store this easily
if (1 == preg_match('/^Plan - (.+) \((.+)\)$/', $invoice_item->product_key, $matches)) {
$plan = strtolower($matches[1]);
$term = strtolower($matches[2]);
} elseif ($invoice_item->product_key == 'Pending Monthly') {
$pending_monthly = true;
}
}
if (!empty($plan)) {
$account = Account::with('users')->find($invoice->client->public_id);
if(
$account->company->plan != $plan
|| DateTime::createFromFormat('Y-m-d', $account->company->plan_expires) >= date_create('-7 days')
) {
// Either this is a different plan, or the subscription expired more than a week ago
// Reset any grandfathering
$account->company->plan_started = date_create()->format('Y-m-d');
}
if (
$account->company->plan == $plan
&& $account->company->plan_term == $term
&& DateTime::createFromFormat('Y-m-d', $account->company->plan_expires) >= date_create()
) {
// This is a renewal; mark it paid as of when this term expires
$account->company->plan_paid = $account->company->plan_expires;
} else {
$account->company->plan_paid = date_create()->format('Y-m-d');
}
$account->company->payment_id = $payment->id;
$account->company->plan = $plan;
$account->company->plan_term = $term;
$account->company->plan_expires = DateTime::createFromFormat('Y-m-d', $account->company->plan_paid)
->modify($term == PLAN_TERM_MONTHLY ? '+1 month' : '+1 year')->format('Y-m-d');
if (!empty($pending_monthly)) {
$account->company->pending_plan = $plan;
$account->company->pending_term = PLAN_TERM_MONTHLY;
} else {
$account->company->pending_plan = null;
$account->company->pending_term = null;
}
$account->company->save();
}
}
return $payment;
}
private function parseCardType($cardName) {
$cardTypes = array(
'visa' => PAYMENT_TYPE_VISA,
'americanexpress' => PAYMENT_TYPE_AMERICAN_EXPRESS,
'amex' => PAYMENT_TYPE_AMERICAN_EXPRESS,
'mastercard' => PAYMENT_TYPE_MASTERCARD,
'discover' => PAYMENT_TYPE_DISCOVER,
'jcb' => PAYMENT_TYPE_JCB,
'dinersclub' => PAYMENT_TYPE_DINERS,
'carteblanche' => PAYMENT_TYPE_CARTE_BLANCHE,
'chinaunionpay' => PAYMENT_TYPE_UNIONPAY,
'unionpay' => PAYMENT_TYPE_UNIONPAY,
'laser' => PAYMENT_TYPE_LASER,
'maestro' => PAYMENT_TYPE_MAESTRO,
'solo' => PAYMENT_TYPE_SOLO,
'switch' => PAYMENT_TYPE_SWITCH
);
$cardName = strtolower(str_replace(array(' ', '-', '_'), '', $cardName));
if (empty($cardTypes[$cardName]) && 1 == preg_match('/^('.implode('|', array_keys($cardTypes)).')/', $cardName, $matches)) {
// Some gateways return extra stuff after the card name
$cardName = $matches[1];
}
if (!empty($cardTypes[$cardName])) {
return $cardTypes[$cardName];
} else {
return PAYMENT_TYPE_CREDIT_CARD_OTHER;
}
}
private function detectCardType($number)
{
if (preg_match('/^3[47][0-9]{13}$/',$number)) {
return PAYMENT_TYPE_AMERICAN_EXPRESS;
} elseif (preg_match('/^3(?:0[0-5]|[68][0-9])[0-9]{11}$/',$number)) {
return PAYMENT_TYPE_DINERS;
} elseif (preg_match('/^6(?:011|5[0-9][0-9])[0-9]{12}$/',$number)) {
return PAYMENT_TYPE_DISCOVER;
} elseif (preg_match('/^(?:2131|1800|35\d{3})\d{11}$/',$number)) {
return PAYMENT_TYPE_JCB;
} elseif (preg_match('/^5[1-5][0-9]{14}$/',$number)) {
return PAYMENT_TYPE_MASTERCARD;
} elseif (preg_match('/^4[0-9]{12}(?:[0-9]{3})?$/',$number)) {
return PAYMENT_TYPE_VISA;
}
return PAYMENT_TYPE_CREDIT_CARD_OTHER;
}
public function completePurchase($gateway, $accountGateway, $details, $token)
{
if ($accountGateway->isGateway(GATEWAY_MOLLIE)) {
$details['transactionReference'] = $token;
$response = $gateway->fetchTransaction($details)->send();
return $gateway->fetchTransaction($details)->send();
} else {
return $gateway->completePurchase($details)->send();
}
}
public function autoBillInvoice($invoice)
{
$client = $invoice->client;
// Make sure we've migrated in data from Stripe
$this->getClientPaymentMethods($client);
$account = $client->account;
$invitation = $invoice->invitations->first();
$token = $client->getGatewayToken($accountGateway/* return parameter */, $accountGatewayToken/* return parameter */);
if (!$accountGatewayToken) {
if ( ! $invitation) {
return false;
}
$defaultPaymentMethod = $accountGatewayToken->default_payment_method;
$paymentDriver = $account->paymentDriver($invitation, GATEWAY_TYPE_TOKEN);
$customer = $paymentDriver->customer();
if (!$invitation || !$token || !$defaultPaymentMethod) {
if ( ! $customer) {
return false;
}
if ($defaultPaymentMethod->requiresDelayedAutoBill()) {
$paymentMethod = $customer->default_payment_method;
if ($paymentMethod->requiresDelayedAutoBill()) {
$invoiceDate = \DateTime::createFromFormat('Y-m-d', $invoice->invoice_date);
$minDueDate = clone $invoiceDate;
$minDueDate->modify('+10 days');
@ -906,43 +233,14 @@ class PaymentService extends BaseService
}
}
// setup the gateway/payment info
$details = $this->getPaymentDetails($invitation, $accountGateway);
$details['customerReference'] = $token;
$details['token'] = $defaultPaymentMethod->source_reference;
$details['paymentType'] = $defaultPaymentMethod->payment_type_id;
return $paymentDriver->completeOnsitePurchase(false, $paymentMethod);
/*
if ($accountGateway->gateway_id == GATEWAY_WEPAY) {
$details['transaction_id'] = 'autobill_'.$invoice->id;
}
// submit purchase/get response
$response = $this->purchase($accountGateway, $details);
if ($response->isSuccessful()) {
$ref = $response->getTransactionReference();
return $this->createPayment($invitation, $accountGateway, $ref, null, $details, $defaultPaymentMethod, $response);
} else {
return false;
}
}
public function getClientDefaultPaymentMethod($client) {
$this->getClientPaymentMethods($client);
$client->getGatewayToken($accountGateway/* return parameter */, $accountGatewayToken/* return parameter */);
if (!$accountGatewayToken) {
return false;
}
return $accountGatewayToken->default_payment_method;
}
public function getClientRequiresDelayedAutoBill($client) {
$defaultPaymentMethod = $this->getClientDefaultPaymentMethod($client);
return $defaultPaymentMethod?$defaultPaymentMethod->requiresDelayedAutoBill():null;
*/
}
public function getDatatable($clientPublicId, $search)
@ -971,7 +269,9 @@ class PaymentService extends BaseService
foreach ($payments as $payment) {
if (Auth::user()->can('edit', $payment)) {
$amount = !empty($params['amount']) ? floatval($params['amount']) : null;
if ($this->refund($payment, $amount)) {
$accountGateway = $payment->account_gateway;
$paymentDriver = $accountGateway->paymentDriver();
if ($paymentDriver->refundPayment($payment, $amount)) {
$successful++;
}
}
@ -983,201 +283,4 @@ class PaymentService extends BaseService
}
}
public function refund($payment, $amount = null) {
if ($amount) {
$amount = min($amount, $payment->amount - $payment->refunded);
}
$accountGateway = $payment->account_gateway;
if (!$accountGateway) {
$accountGateway = AccountGateway::withTrashed()->find($payment->account_gateway_id);
}
if (!$amount || !$accountGateway) {
return;
}
if ($payment->payment_type_id != PAYMENT_TYPE_CREDIT) {
$gateway = $this->createGateway($accountGateway);
$details = array(
'transactionReference' => $payment->transaction_reference,
);
if ($accountGateway->gateway_id == GATEWAY_WEPAY && $amount == $payment->getCompletedAmount()) {
// WePay issues a full refund when no amount is set. If an amount is set, it will try
// to issue a partial refund without refunding any fees. However, the Stripe driver
// (but not the API) requires the amount parameter to be set no matter what.
} else {
$details['amount'] = $amount;
}
if ($accountGateway->gateway_id == GATEWAY_WEPAY) {
$details['refund_reason'] = 'Refund issued by merchant.';
}
$refund = $gateway->refund($details);
$response = $refund->send();
if ($response->isSuccessful()) {
$payment->recordRefund($amount);
} else {
$data = $response->getData();
if ($data instanceof \Braintree\Result\Error) {
$error = $data->errors->deepAll()[0];
if ($error && $error->code == 91506) {
$tryVoid = true;
}
} elseif ($accountGateway->gateway_id == GATEWAY_WEPAY && $response->getCode() == 4004) {
$tryVoid = true;
}
if (!empty($tryVoid)) {
if ($amount == $payment->amount) {
// This is an unsettled transaction; try to void it
$void = $gateway->void(array(
'transactionReference' => $payment->transaction_reference,
));
$response = $void->send();
if ($response->isSuccessful()) {
$payment->markVoided();
}
} else {
$this->error('Unknown', 'Partial refund not allowed for unsettled transactions.', $accountGateway);
return false;
}
}
if (!$response->isSuccessful()) {
$this->error('Unknown', $response->getMessage(), $accountGateway);
return false;
}
}
} else {
$payment->recordRefund($amount);
}
return true;
}
private function error($type, $error, $accountGateway = false, $exception = false)
{
$message = '';
if ($accountGateway && $accountGateway->gateway) {
$message = $accountGateway->gateway->name . ': ';
}
$message .= $error ?: trans('texts.payment_error');
Session::flash('error', $message);
Utils::logError("Payment Error [{$type}]: " . ($exception ? Utils::getErrorString($exception) : $message), 'PHP', true);
}
public function makeStripeCall($accountGateway, $method, $url, $body = null) {
$apiKey = $accountGateway->getConfig()->apiKey;
if (!$apiKey) {
return 'No API key set';
}
try{
$options = [
'headers' => ['content-type' => 'application/x-www-form-urlencoded'],
'auth' => [$accountGateway->getConfig()->apiKey,''],
];
if ($body) {
$options['body'] = $body;
}
$response = (new \GuzzleHttp\Client(['base_uri'=>'https://api.stripe.com/v1/']))->request(
$method,
$url,
$options
);
return json_decode($response->getBody(), true);
} catch (\GuzzleHttp\Exception\BadResponseException $e) {
$response = $e->getResponse();
$body = json_decode($response->getBody(), true);
if ($body && $body['error'] && $body['error']['type'] == 'invalid_request_error') {
return $body['error']['message'];
}
return $e->getMessage();
}
}
private function getPlaidToken($accountGateway, $publicToken, $accountId) {
$clientId = $accountGateway->getPlaidClientId();
$secret = $accountGateway->getPlaidSecret();
if (!$clientId) {
return 'No client ID set';
}
if (!$secret) {
return 'No secret set';
}
try{
$subdomain = $accountGateway->getPlaidEnvironment() == 'production' ? 'api' : 'tartan';
$response = (new \GuzzleHttp\Client(['base_uri'=>"https://{$subdomain}.plaid.com"]))->request(
'POST',
'exchange_token',
[
'allow_redirects' => false,
'headers' => ['content-type' => 'application/x-www-form-urlencoded'],
'body' => http_build_query(array(
'client_id' => $clientId,
'secret' => $secret,
'public_token' => $publicToken,
'account_id' => $accountId,
))
]
);
return json_decode($response->getBody(), true);
} catch (\GuzzleHttp\Exception\BadResponseException $e) {
$response = $e->getResponse();
$body = json_decode($response->getBody(), true);
if ($body && !empty($body['message'])) {
return $body['message'];
}
return $e->getMessage();
}
}
public function purchase($accountGateway, $details) {
$gateway = $this->createGateway($accountGateway);
if ($accountGateway->gateway_id == GATEWAY_WEPAY) {
$details['applicationFee'] = $this->calculateApplicationFee($accountGateway, $details['amount']);
$details['feePayer'] = WEPAY_FEE_PAYER;
$details['callbackUri'] = $accountGateway->getWebhookUrl();
if(isset($details['paymentType'])) {
if($details['paymentType'] == PAYMENT_TYPE_ACH || $details['paymentType'] == PAYMENT_TYPE_WEPAY_ACH) {
$details['paymentMethodType'] = 'payment_bank';
}
unset($details['paymentType']);
}
}
$response = $gateway->purchase($details)->send();
return $response;
}
private function calculateApplicationFee($accountGateway, $amount) {
if ($accountGateway->gateway_id = GATEWAY_WEPAY) {
$fee = WEPAY_APP_FEE_MULTIPLIER * $amount + WEPAY_APP_FEE_FIXED;
return floor(min($fee, $amount * 0.2));// Maximum fee is 20% of the amount.
}
return 0;
}
}

View File

@ -55,9 +55,8 @@ class TemplateService
];
// Add variables for available payment types
foreach (Gateway::$paymentTypes as $type) {
$camelType = Gateway::getPaymentTypeName($type);
$type = Utils::toSnakeCase($camelType);
foreach (Gateway::$gatewayTypes as $type) {
$camelType = Utils::toCamelCase($type);
$variables["\${$camelType}Link"] = $invitation->getLink('payment') . "/{$type}";
$variables["\${$camelType}Button"] = Form::emailPaymentButton($invitation->getLink('payment') . "/{$type}");
}

View File

@ -28,6 +28,7 @@ class PaymentsChanges extends Migration
{
$table->increments('id');
$table->unsignedInteger('account_id');
$table->unsignedInteger('user_id');
$table->unsignedInteger('contact_id')->nullable();
$table->unsignedInteger('account_gateway_token_id');
$table->unsignedInteger('payment_type_id');
@ -44,6 +45,7 @@ class PaymentsChanges extends Migration
$table->softDeletes();
$table->foreign('account_id')->references('id')->on('accounts')->onDelete('cascade');
$table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
$table->foreign('contact_id')->references('id')->on('contacts')->onDelete('cascade');
$table->foreign('account_gateway_token_id')->references('id')->on('account_gateway_tokens');
$table->foreign('payment_type_id')->references('id')->on('payment_types');

View File

@ -16,18 +16,18 @@ class PaymentLibrariesSeeder extends Seeder
$gateways = [
['name' => 'Authorize.Net AIM', 'provider' => 'AuthorizeNet_AIM'],
['name' => 'Authorize.Net SIM', 'provider' => 'AuthorizeNet_SIM'],
['name' => 'Authorize.Net SIM', 'provider' => 'AuthorizeNet_SIM', 'payment_library_id' => 2],
['name' => 'CardSave', 'provider' => 'CardSave'],
['name' => 'Eway Rapid', 'provider' => 'Eway_Rapid'],
['name' => 'Eway Rapid', 'provider' => 'Eway_Rapid', 'is_offsite' => true],
['name' => 'FirstData Connect', 'provider' => 'FirstData_Connect'],
['name' => 'GoCardless', 'provider' => 'GoCardless', 'is_offsite' => true],
['name' => 'Migs ThreeParty', 'provider' => 'Migs_ThreeParty'],
['name' => 'Migs TwoParty', 'provider' => 'Migs_TwoParty'],
['name' => 'Mollie', 'provider' => 'Mollie'],
['name' => 'Mollie', 'provider' => 'Mollie', 'is_offsite' => true],
['name' => 'MultiSafepay', 'provider' => 'MultiSafepay'],
['name' => 'Netaxept', 'provider' => 'Netaxept'],
['name' => 'NetBanx', 'provider' => 'NetBanx'],
['name' => 'PayFast', 'provider' => 'PayFast'],
['name' => 'PayFast', 'provider' => 'PayFast', 'is_offsite' => true],
['name' => 'Payflow Pro', 'provider' => 'Payflow_Pro'],
['name' => 'PaymentExpress PxPay', 'provider' => 'PaymentExpress_PxPay'],
['name' => 'PaymentExpress PxPost', 'provider' => 'PaymentExpress_PxPost'],
@ -41,7 +41,7 @@ class PaymentLibrariesSeeder extends Seeder
['name' => 'TargetPay Direct eBanking', 'provider' => 'TargetPay_Directebanking'],
['name' => 'TargetPay Ideal', 'provider' => 'TargetPay_Ideal'],
['name' => 'TargetPay Mr Cash', 'provider' => 'TargetPay_Mrcash'],
['name' => 'TwoCheckout', 'provider' => 'TwoCheckout'],
['name' => 'TwoCheckout', 'provider' => 'TwoCheckout', 'is_offsite' => true],
['name' => 'WorldPay', 'provider' => 'WorldPay'],
['name' => 'BeanStream', 'provider' => 'BeanStream', 'payment_library_id' => 2],
['name' => 'Psigate', 'provider' => 'Psigate', 'payment_library_id' => 2],

View File

@ -68,7 +68,8 @@ class UserTableSeeder extends Seeder
'city' => $faker->city,
'state' => $faker->state,
'postal_code' => $faker->postcode,
'country_id' => Country::all()->random()->id,
'country_id' => DEFAULT_COUNTRY,
'currency_id' => DEFAULT_CURRENCY,
]);
Contact::create([
@ -77,6 +78,7 @@ class UserTableSeeder extends Seeder
'client_id' => $client->id,
'public_id' => 1,
'email' => TEST_USERNAME,
'is_primary' => true,
]);
Product::create([

View File

@ -445,7 +445,7 @@ $LANG = array(
'token_billing_4' => 'Always',
'token_billing_checkbox' => 'Store credit card details',
'view_in_gateway' => 'View in :gateway',
'use_card_on_file' => 'Use card on file',
'use_card_on_file' => 'Use Card on File',
'edit_payment_details' => 'Edit payment details',
'token_billing' => 'Save card details',
'token_billing_secure' => 'The data is stored securely by :link',
@ -1351,6 +1351,12 @@ $LANG = array(
'update_font_cache' => 'Please force refresh the page to update the font cache.',
'more_options' => 'More options',
'credit_card' => 'Credit Card',
'bank_transfer' => 'Bank Transfer',
'no_transaction_reference' => 'We did not recieve a payment transaction reference from the gateway.',
'use_bank_on_file' => 'Use Bank on File',
'auto_bill_email_message' => 'This invoice will automatically be billed to the payment method on file on the due date.',
'bitcoin' => 'Bitcoin',
);

View File

@ -13,8 +13,7 @@
{!! Former::open($url)->method($method)->rule()->addClass('warn-on-exit') !!}
@if ($accountGateway)
{!! Former::populateField('gateway_id', $accountGateway->gateway_id) !!}
{!! Former::populateField('payment_type_id', $paymentTypeId) !!}
{!! Former::populateField('primary_gateway_id', $accountGateway->gateway_id) !!}
{!! Former::populateField('recommendedGateway_id', $accountGateway->gateway_id) !!}
{!! Former::populateField('show_address', intval($accountGateway->show_address)) !!}
{!! Former::populateField('update_address', intval($accountGateway->update_address)) !!}
@ -39,14 +38,13 @@
{!! Former::populateField('update_address', 1) !!}
@if (Utils::isNinjaDev())
{!! Former::populateField('23_apiKey', env('STRIPE_TEST_SECRET_KEY')) !!}
{!! Former::populateField('publishable_key', env('STRIPE_TEST_PUBLISHABLE_KEY')) !!}
@include('accounts.partials.payment_credentials')
@endif
@endif
@if ($accountGateway)
<div style="display: none">
{!! Former::text('primary_gateway_id')->value($accountGateway->gateway_id) !!}
{!! Former::text('primary_gateway_id') !!}
</div>
@else
{!! Former::select('primary_gateway_id')
@ -203,6 +201,12 @@
} else {
$('.onsite-fields').show();
}
if (gateway.id == {{ GATEWAY_STRIPE }}) {
$('.stripe-ach').show();
} else {
$('.stripe-ach').hide();
}
}
function gatewayLink(url) {

View File

@ -62,18 +62,9 @@
->label('Accepted Credit Cards')
->checkboxes($creditCardTypes)
->class('creditcard-types') !!}
@if ($account->getGatewayByType(PAYMENT_TYPE_DIRECT_DEBIT))
{!! Former::checkbox('enable_ach')
->label(trans('texts.ach'))
->text(trans('texts.enable_ach'))
->value(null)
->disabled(true)
->help(trans('texts.ach_disabled')) !!}
@else
{!! Former::checkbox('enable_ach')
->label(trans('texts.ach'))
->text(trans('texts.enable_ach')) !!}
@endif
{!! Former::checkbox('tos_agree')->label(' ')->text(trans('texts.wepay_tos_agree',
['link'=>'<a id="wepay-tos-link" href="https://go.wepay.com/terms-of-service-us" target="_blank">'.trans('texts.wepay_tos_link_text').'</a>']
@ -83,7 +74,7 @@
<br/>
<center>
{!! Button::normal(trans('texts.use_another_provider'))->large()->asLinkTo(URL::to('/gateways/create/0')) !!}
{!! Button::normal(trans('texts.use_another_provider'))->large()->asLinkTo(URL::to('/gateways/create?other_providers=true')) !!}
{!! Button::success(trans('texts.sign_up_with_wepay'))->submit()->large() !!}
</center>
@ -108,7 +99,7 @@
})
</script>
<input type="hidden" name="gateway_id" value="{{ GATEWAY_WEPAY }}">
<input type="hidden" name="primary_gateway_id" value="{{ GATEWAY_WEPAY }}">
{!! Former::close() !!}
@stop

View File

@ -0,0 +1,47 @@
{!! Former::populateField(GATEWAY_STRIPE . '_apiKey', env('STRIPE_TEST_SECRET_KEY')) !!}
{!! Former::populateField('publishable_key', env('STRIPE_TEST_PUBLISHABLE_KEY')) !!}
{!! Former::populateField('enable_ach', 1) !!}
{!! Former::populateField('plaid_client_id', env('PLAID_TEST_CLIENT_ID')) !!}
{!! Former::populateField('plaid_secret', env('PLAID_TEST_SECRET')) !!}
{!! Former::populateField('plaid_public_key', env('PLAID_TEST_PUBLIC_KEY')) !!}
{!! Former::populateField(GATEWAY_PAYPAL_EXPRESS . '_username', env('PAYPAL_TEST_USERNAME')) !!}
{!! Former::populateField(GATEWAY_PAYPAL_EXPRESS . '_password', env('PAYPAL_TEST_PASSWORD')) !!}
{!! Former::populateField(GATEWAY_PAYPAL_EXPRESS . '_signature', env('PAYPAL_TEST_SIGNATURE')) !!}
{!! Former::populateField(GATEWAY_PAYPAL_EXPRESS . '_testMode', 1) !!}
{!! Former::populateField(GATEWAY_DWOLLA . '_destinationId', env('DWOLLA_TEST_DESTINATION_ID')) !!}
{!! Former::populateField(GATEWAY_BITPAY . '_testMode', 1) !!}
{!! Former::populateField(GATEWAY_BITPAY . '_apiKey', env('BITPAY_TEST_API_KEY')) !!}
{!! Former::populateField(GATEWAY_TWO_CHECKOUT . '_secretWord', env('TWO_CHECKOUT_SECRET_WORD')) !!}
{!! Former::populateField(GATEWAY_TWO_CHECKOUT . '_accountNumber', env('TWO_CHECKOUT_ACCOUNT_NUMBER')) !!}
{!! Former::populateField(GATEWAY_TWO_CHECKOUT . '_testMode', 1) !!}
{!! Former::populateField(GATEWAY_MOLLIE . '_apiKey', env('MOLLIE_TEST_API_KEY')) !!}
{!! Former::populateField(GATEWAY_AUTHORIZE_NET . '_apiLoginId', env('AUTHORIZE_NET_TEST_API_LOGIN_ID')) !!}
{!! Former::populateField(GATEWAY_AUTHORIZE_NET . '_transactionKey', env('AUTHORIZE_NET_TEST_TRANSACTION_KEY')) !!}
{!! Former::populateField(GATEWAY_AUTHORIZE_NET . '_secretKey', env('AUTHORIZE_NET_TEST_SECRET_KEY')) !!}
{!! Former::populateField(GATEWAY_AUTHORIZE_NET . '_testMode', 1) !!}
{!! Former::populateField(GATEWAY_AUTHORIZE_NET . '_developerMode', 1) !!}
{!! Former::populateField(GATEWAY_CHECKOUT_COM . '_publicApiKey', env('CHECKOUT_COM_TEST_PUBLIC_API_KEY')) !!}
{!! Former::populateField(GATEWAY_CHECKOUT_COM . '_secretApiKey', env('CHECKOUT_COM_TEST_SECRET_API_KEY')) !!}
{!! Former::populateField(GATEWAY_CHECKOUT_COM . '_testMode', 1) !!}
{!! Former::populateField(GATEWAY_CYBERSOURCE . '_accessKey', env('CYBERSOURCE_TEST_ACCESS_KEY')) !!}
{!! Former::populateField(GATEWAY_CYBERSOURCE . '_secretKey', env('CYBERSOURCE_TEST_SECRET_KEY')) !!}
{!! Former::populateField(GATEWAY_CYBERSOURCE . '_profileId', env('CYBERSOURCE_TEST_PROFILE_ID')) !!}
{!! Former::populateField(GATEWAY_CYBERSOURCE . '_testMode', 1) !!}
{!! Former::populateField(GATEWAY_EWAY . '_apiKey', env('EWAY_TEST_API_KEY')) !!}
{!! Former::populateField(GATEWAY_EWAY . '_password', env('EWAY_TEST_PASSWORD')) !!}
{!! Former::populateField(GATEWAY_EWAY . '_testMode', 1) !!}
{!! Former::populateField(GATEWAY_BRAINTREE . '_privateKey', env('BRAINTREE_TEST_PRIVATE_KEY')) !!}
{!! Former::populateField(GATEWAY_BRAINTREE . '_publicKey', env('BRAINTREE_TEST_PUBLIC_KEY')) !!}
{!! Former::populateField(GATEWAY_BRAINTREE . '_merchantId', env('BRAINTREE_TEST_MERCHANT_ID')) !!}
{!! Former::populateField(GATEWAY_BRAINTREE . '_testMode', 1) !!}
{!! Former::populateField('enable_paypal', 1) !!}

View File

@ -31,10 +31,12 @@
</div>
{!! Former::close() !!}
<!--
<label for="trashed" style="font-weight:normal; margin-left: 10px;">
<input id="trashed" type="checkbox" onclick="setTrashVisible()"
{{ Session::get("show_trash:gateway") ? 'checked' : ''}}/>&nbsp; {{ trans('texts.show_archived_deleted')}} {{ Utils::transFlowText('gateways') }}
</label>
-->
@if ($showAdd)
{!! Button::primary(trans('texts.add_gateway'))
@ -48,14 +50,13 @@
{!! Datatable::table()
->addColumn(
trans('texts.name'),
trans('texts.payment_type_id'),
trans('texts.action'))
->setUrl(url('api/gateways/'))
->setOptions('sPaginationType', 'bootstrap')
->setOptions('bFilter', false)
->setOptions('bAutoWidth', false)
->setOptions('aoColumns', [[ "sWidth"=> "50%" ], [ "sWidth"=> "30%" ], ["sWidth"=> "20%"]])
->setOptions('aoColumnDefs', [['bSortable'=>false, 'aTargets'=>[2]]])
->setOptions('aoColumns', [[ "sWidth"=> "80%" ], ["sWidth"=> "20%"]])
->setOptions('aoColumnDefs', [['bSortable'=>false, 'aTargets'=>[1]]])
->render('datatable') !!}
<script>

View File

@ -132,10 +132,10 @@
<li>$customInvoice1</li>
@endif
@if (count($account->account_gateways) > 1)
@foreach (\App\Models\Gateway::$paymentTypes as $type)
@foreach (\App\Models\Gateway::$gatewayTypes as $type)
@if ($account->getGatewayByType($type))
<li>${{ \App\Models\Gateway::getPaymentTypeName($type) }}Link</li>
<li>${{ \App\Models\Gateway::getPaymentTypeName($type) }}Button</li>
<li>${{ Utils::toCamelCase($type) }}Link</li>
<li>${{ Utils::toCamelCase($type) }}Button</li>
@endif
@endforeach
@endif
@ -274,11 +274,11 @@
vals.push('custom value', 'custom value', 'custom value', 'custom value');
// Add any available payment method links
@foreach (\App\Models\Gateway::$paymentTypes as $type)
{!! "keys.push('" . \App\Models\Gateway::getPaymentTypeName($type).'Link' . "');" !!}
@foreach (\App\Models\Gateway::$gatewayTypes as $type)
{!! "keys.push('" . Utils::toCamelCase($type).'Link' . "');" !!}
{!! "vals.push('" . URL::to('/payment/...') . "');" !!}
{!! "keys.push('" . \App\Models\Gateway::getPaymentTypeName($type).'Button' . "');" !!}
{!! "keys.push('" . Utils::toCamelCase($type).'Button' . "');" !!}
{!! "vals.push('" . Form::flatButton('pay_now', '#36c157') . "');" !!}
@endforeach

View File

@ -39,7 +39,9 @@
</div>
@if ($gatewayLink)
{!! Button::normal(trans('texts.view_in_gateway', ['gateway'=>$gateway->gateway->name]))->asLinkTo($gatewayLink)->withAttributes(['target' => '_blank']) !!}
{!! Button::normal(trans('texts.view_in_gateway', ['gateway'=>$gatewayName]))
->asLinkTo($gatewayLink)
->withAttributes(['target' => '_blank']) !!}
@endif
@if ($client->trashed())

View File

@ -108,7 +108,7 @@
</div>
</div>
@if ($account->isPro())
@if ($account->hasFeature(FEATURE_DOCUMENTS))
<div clas="row">
<div class="col-md-2 col-sm-4"><div class="control-label" style="margin-bottom:10px;">{{trans('texts.expense_documents')}}</div></div>
<div class="col-md-12 col-sm-8">

View File

@ -161,14 +161,15 @@
</div>
</div>
</div>
@if (!empty($account->getTokenGatewayId()))
<div class="row">
<div class="col-xs-12">
<h3>{{ trans('texts.payment_methods') }}</h3>
@include('payments.paymentmethods_list')
</div>
</div>
@endif
<div style="min-height: 550px">
{!! Datatable::table()
->addColumn(

View File

@ -22,15 +22,17 @@
}
</style>
@if (!empty($braintreeClientToken))
@if ($accountGateway->gateway_id == GATEWAY_BRAINTREE && !empty($transactionToken))
<div id="paypal-container"></div>
<script type="text/javascript" src="https://js.braintreegateway.com/js/braintree-2.23.0.min.js"></script>
<script type="text/javascript" >
$(function() {
var paypalLink = $('.dropdown-menu a[href$="/braintree_paypal"]'),
var paypalLink = $('.dropdown-menu a[href$="paypal"]'),
paypalUrl = paypalLink.attr('href'),
checkout;
console.log(paypalUrl);
paypalLink.parent().attr('id', 'paypal-container');
braintree.setup("{{ $braintreeClientToken }}", "custom", {
braintree.setup("{{ $transactionToken }}", "custom", {
onReady: function (integration) {
checkout = integration;
$('.dropdown-menu a[href$="#braintree_paypal"]').each(function(){
@ -50,7 +52,7 @@
paypal: true
},
onPaymentMethodReceived: function (obj) {
window.location.href = paypalUrl + '/' + encodeURIComponent(obj.nonce) + "?details=" + encodeURIComponent(JSON.stringify(obj.details))
window.location.href = paypalUrl.replace('#braintree_paypal', '') + '/' + encodeURIComponent(obj.nonce) + "?device_data=" + encodeURIComponent(JSON.stringify(obj.details));
}
});
paypalLink.click(function(e){
@ -95,8 +97,8 @@
<div class="container">
@if ($checkoutComToken)
@include('partials.checkout_com_payment')
@if ($partialView)
@include($partialView)
@else
<div class="pull-right" style="text-align:right">
@if ($invoice->isQuote())
@ -118,12 +120,13 @@
@endif
@endif
</div>
@endif
<div class="pull-left">
@if(!empty($documentsZipURL))
{!! Button::normal(trans('texts.download_documents', array('size'=>Form::human_filesize($documentsZipSize))))->asLinkTo($documentsZipURL)->large() !!}
@endif
</div>
@endif
<div class="clearfix"></div><p>&nbsp;</p>
@if ($account->isPro() && $invoice->hasDocuments())

View File

@ -1,524 +0,0 @@
@extends('public.header')
@section('head')
@parent
@if (!empty($braintreeClientToken))
@include('payments.tokenization_braintree')
@elseif (isset($accountGateway) && $accountGateway->getPublishableStripeKey())
@include('payments.tokenization_stripe')
@elseif (isset($accountGateway) && $accountGateway->gateway_id == GATEWAY_WEPAY && $paymentType != PAYMENT_TYPE_WEPAY_ACH)
@include('payments.tokenization_wepay')
@else
<script type="text/javascript">
$(function() {
$('.payment-form').submit(function(event) {
var $form = $(this);
// Disable the submit button to prevent repeated clicks
$form.find('button').prop('disabled', true);
return true;
});
});
</script>
@endif
@stop
@section('content')
@include('payments.payment_css')
@if($paymentType == PAYMENT_TYPE_STRIPE_ACH)
{!! Former::open($url)
->autocomplete('on')
->addClass('payment-form')
->id('payment-form')
->rules(array(
'first_name' => 'required',
'last_name' => 'required',
'account_number' => 'required',
'routing_number' => 'required',
'account_holder_name' => 'required',
'account_holder_type' => 'required',
'authorize_ach' => 'required',
)) !!}
@else
{!! Former::vertical_open($url)
->autocomplete('on')
->addClass('payment-form')
->id('payment-form')
->rules(array(
'first_name' => 'required',
'last_name' => 'required',
'card_number' => 'required',
'expiration_month' => 'required',
'expiration_year' => 'required',
'cvv' => 'required',
'address1' => 'required',
'city' => 'required',
'state' => 'required',
'postal_code' => 'required',
'country_id' => 'required',
'phone' => 'required',
'email' => 'required|email',
'authorize_ach' => 'required',
'tos_agree' => 'required',
)) !!}
@endif
@if ($client)
{{ Former::populate($client) }}
{{ Former::populateField('first_name', $contact->first_name) }}
{{ Former::populateField('last_name', $contact->last_name) }}
{{ Former::populateField('email', $contact->email) }}
@if (!$client->country_id && $client->account->country_id)
{{ Former::populateField('country_id', $client->account->country_id) }}
@endif
@if (!$client->currency_id && $client->account->currency_id)
{{ Former::populateField('currency_id', $client->account->currency_id) }}
{{ Former::populateField('currency', $client->account->currency->code) }}
@endif
@endif
@if (Utils::isNinjaDev())
{{ Former::populateField('first_name', 'Test') }}
{{ Former::populateField('last_name', 'Test') }}
{{ Former::populateField('address1', '350 5th Ave') }}
{{ Former::populateField('city', 'New York') }}
{{ Former::populateField('state', 'NY') }}
{{ Former::populateField('postal_code', '10118') }}
{{ Former::populateField('country_id', 840) }}
<script>
$(function() {
$('#card_number').val('4242424242424242');
$('#cvv').val('1234');
$('#expiration_month').val(1);
$('#expiration_year').val({{ date_create()->modify('+3 year')->format('Y') }});
})
</script>
@endif
<div class="container">
<p>&nbsp;</p>
<div class="panel panel-default">
<div class="panel-body">
<div class="row">
<div class="col-md-7">
<header>
@if ($client && isset($invoiceNumber))
<h2>{{ $client->getDisplayName() }}</h2>
<h3>{{ trans('texts.invoice') . ' ' . $invoiceNumber }}<span>|&nbsp; {{ trans('texts.amount_due') }}: <em>{{ $account->formatMoney($amount, $client, true) }}</em></span></h3>
@elseif ($paymentTitle)
<h2>{{ $paymentTitle }}
@if(isset($paymentSubtitle))
<br/><small>{{ $paymentSubtitle }}</small>
@endif
</h2>
@endif
</header>
</div>
<div class="col-md-5">
@if (Request::secure() || Utils::isNinjaDev())
<div class="secure">
<h3>{{ trans('texts.secure_payment') }}</h3>
<div>{{ trans('texts.256_encryption') }}</div>
</div>
@endif
</div>
</div>
<p>&nbsp;<br/>&nbsp;</p>
<div>
@if($paymentType != PAYMENT_TYPE_STRIPE_ACH && $paymentType != PAYMENT_TYPE_BRAINTREE_PAYPAL && $paymentType != PAYMENT_TYPE_WEPAY_ACH)
<h3>{{ trans('texts.contact_information') }}</h3>
<div class="row">
<div class="col-md-6">
{!! Former::text('first_name')
->placeholder(trans('texts.first_name'))
->label('') !!}
</div>
<div class="col-md-6">
{!! Former::text('last_name')
->placeholder(trans('texts.last_name'))
->autocomplete('family-name')
->label('') !!}
</div>
</div>
@if (isset($paymentTitle) || ! empty($contact->email))
<div class="row" style="display:{{ isset($paymentTitle) ? 'block' : 'none' }}">
<div class="col-md-12">
{!! Former::text('email')
->placeholder(trans('texts.email'))
->autocomplete('email')
->label('') !!}
</div>
</div>
@endif
<p>&nbsp;<br/>&nbsp;</p>
@if (!empty($showAddress))
<h3>{{ trans('texts.billing_address') }}&nbsp;<span class="help">{{ trans('texts.payment_footer1') }}</span></h3>
<div class="row">
<div class="col-md-6">
{!! Former::text('address1')
->autocomplete('address-line1')
->placeholder(trans('texts.address1'))
->label('') !!}
</div>
<div class="col-md-6">
{!! Former::text('address2')
->autocomplete('address-line2')
->placeholder(trans('texts.address2'))
->label('') !!}
</div>
</div>
<div class="row">
<div class="col-md-6">
{!! Former::text('city')
->autocomplete('address-level2')
->placeholder(trans('texts.city'))
->label('') !!}
</div>
<div class="col-md-6">
{!! Former::text('state')
->autocomplete('address-level1')
->placeholder(trans('texts.state'))
->label('') !!}
</div>
</div>
<div class="row">
<div class="col-md-6">
{!! Former::text('postal_code')
->autocomplete('postal-code')
->placeholder(trans('texts.postal_code'))
->label('') !!}
</div>
<div class="col-md-6">
{!! Former::select('country_id')
->placeholder(trans('texts.country_id'))
->fromQuery($countries, 'name', 'id')
->addGroupClass('country-select')
->label('') !!}
</div>
</div>
<p>&nbsp;<br/>&nbsp;</p>
@endif
<h3>{{ trans('texts.billing_method') }}</h3>
@endif
@if($paymentType == PAYMENT_TYPE_STRIPE_ACH)
@if($accountGateway->getPlaidEnabled())
<div id="plaid_container">
<a class="btn btn-default btn-lg" id="plaid_link_button">
<img src="{{ URL::to('images/plaid-logo.svg') }}">
<img src="{{ URL::to('images/plaid-logowhite.svg') }}" class="hoverimg">
{{ trans('texts.link_with_plaid') }}
</a>
<div id="plaid_linked">
<div id="plaid_linked_status"></div>
<a href="#" id="plaid_unlink">{{ trans('texts.unlink') }}</a>
</div>
</div>
@endif
<div id="manual_container">
@if($accountGateway->getPlaidEnabled())
<div id="plaid_or"><span>{{ trans('texts.or') }}</span></div>
<h4>{{ trans('texts.link_manually') }}</h4>
@endif
<p>{{ trans('texts.ach_verification_delay_help') }}</p>
{!! Former::radios('account_holder_type')->radios(array(
trans('texts.individual_account') => array('value' => 'individual'),
trans('texts.company_account') => array('value' => 'company'),
))->inline()->label(trans('texts.account_holder_type')); !!}
{!! Former::text('account_holder_name')
->label(trans('texts.account_holder_name')) !!}
{!! Former::select('country_id')
->label(trans('texts.country_id'))
->fromQuery($countries, 'name', 'id')
->addGroupClass('country-select') !!}
{!! Former::select('currency')
->label(trans('texts.currency_id'))
->fromQuery($currencies, 'name', 'code')
->addGroupClass('currency-select') !!}
{!! Former::text('')
->id('routing_number')
->label(trans('texts.routing_number')) !!}
<div class="form-group" style="margin-top:-15px">
<div class="col-md-8 col-md-offset-4">
<div id="bank_name"></div>
</div>
</div>
{!! Former::text('')
->id('account_number')
->label(trans('texts.account_number')) !!}
{!! Former::text('')
->id('confirm_account_number')
->label(trans('texts.confirm_account_number')) !!}
</div>
{!! Former::checkbox('authorize_ach')
->text(trans('texts.ach_authorization', ['company'=>$account->getDisplayName(), 'email' => $account->work_email]))
->label(' ') !!}
<div class="col-md-8 col-md-offset-4">
{!! Button::success(strtoupper(trans('texts.add_account')))
->submit()
->withAttributes(['id'=>'add_account_button'])
->large() !!}
@if($accountGateway->getPlaidEnabled() && !empty($amount))
{!! Button::success(strtoupper(trans('texts.pay_now') . ' - ' . $account->formatMoney($amount, $client, true) ))
->submit()
->withAttributes(['style'=>'display:none', 'id'=>'pay_now_button'])
->large() !!}
@endif
</div>
@elseif($paymentType == PAYMENT_TYPE_BRAINTREE_PAYPAL)
<h3>{{ trans('texts.paypal') }}</h3>
<div>{{$details->firstName}} {{$details->lastName}}</div>
<div>{{$details->email}}</div>
<input type="hidden" name="sourceToken" value="{{$sourceId}}">
<input type="hidden" name="first_name" value="{{$details->firstName}}">
<input type="hidden" name="last_name" value="{{$details->lastName}}">
<input type="hidden" name="email" value="{{$details->email}}">
<p>&nbsp;</p>
@if (isset($amount) && $client && $account->showTokenCheckbox())
<input id="token_billing" type="checkbox" name="token_billing" {{ $account->selectTokenCheckbox() ? 'CHECKED' : '' }} value="1" style="margin-left:0px; vertical-align:top">
<label for="token_billing" class="checkbox" style="display: inline;">{{ trans('texts.token_billing_braintree_paypal') }}</label>
<span class="help-block" style="font-size:15px">
{!! trans('texts.token_billing_secure', ['link' => link_to('https://www.braintreepayments.com/', 'Braintree', ['target' => '_blank'])]) !!}
</span>
@endif
<p>&nbsp;</p>
<center>
@if(isset($amount))
{!! Button::success(strtoupper(trans('texts.pay_now') . ' - ' . $account->formatMoney($amount, $client, true) ))
->submit()
->large() !!}
@else
{!! Button::success(strtoupper(trans('texts.add_paypal_account') ))
->submit()
->large() !!}
@endif
</center>
@elseif($paymentType == PAYMENT_TYPE_WEPAY_ACH)
<h3>{{ trans('texts.bank_account') }}</h3>
@if (!empty($details))
<div>{{$details->bank_account_name}}</div>
<div>&bull;&bull;&bull;&bull;&bull;{{$details->bank_account_last_four}}</div>
@endif
{!! Former::checkbox('authorize_ach')
->text(trans('texts.ach_authorization', ['company'=>$account->getDisplayName(), 'email' => $account->work_email]))
->label(' ') !!}
{!! Former::checkbox('tos_agree')
->text(trans('texts.wepay_payment_tos_agree', [
'terms' => '<a href="https://go.wepay.com/terms-of-service-us" target="_blank">'.trans('texts.terms_of_service').'</a>',
'privacy_policy' => '<a href="https://go.wepay.com/privacy-policy-us" target="_blank">'.trans('texts.privacy_policy').'</a>',
]))
->help(trans('texts.payment_processed_through_wepay'))
->label(' ') !!}
<input type="hidden" name="sourceToken" value="{{$sourceId}}">
<p>&nbsp;</p>
<center>
@if(isset($amount) && empty($paymentMethodPending))
{!! Button::success(strtoupper(trans('texts.pay_now') . ' - ' . $account->formatMoney($amount, $client, true) ))
->submit()
->large() !!}
@else
{!! Button::success(strtoupper(trans('texts.add_bank_account') ))
->submit()
->large() !!}
@endif
</center>
@else
<div class="row">
<div class="col-md-9">
@if (!empty($braintreeClientToken))
<div id="card_number" class="braintree-hosted form-control"></div>
@else
{!! Former::text(!empty($tokenize) ? '' : 'card_number')
->id('card_number')
->placeholder(trans('texts.card_number'))
->autocomplete('cc-number')
->label('') !!}
@endif
</div>
<div class="col-md-3">
@if (!empty($braintreeClientToken))
<div id="cvv" class="braintree-hosted form-control"></div>
@else
{!! Former::text(!empty($tokenize) ? '' : 'cvv')
->id('cvv')
->placeholder(trans('texts.cvv'))
->autocomplete('off')
->label('') !!}
@endif
</div>
</div>
<div class="row">
<div class="col-md-6">
@if (!empty($braintreeClientToken))
<div id="expiration_month" class="braintree-hosted form-control"></div>
@else
{!! Former::select(!empty($tokenize) ? '' : 'expiration_month')
->id('expiration_month')
->autocomplete('cc-exp-month')
->placeholder(trans('texts.expiration_month'))
->addOption('01 - January', '1')
->addOption('02 - February', '2')
->addOption('03 - March', '3')
->addOption('04 - April', '4')
->addOption('05 - May', '5')
->addOption('06 - June', '6')
->addOption('07 - July', '7')
->addOption('08 - August', '8')
->addOption('09 - September', '9')
->addOption('10 - October', '10')
->addOption('11 - November', '11')
->addOption('12 - December', '12')->label('')
!!}
@endif
</div>
<div class="col-md-6">
@if (!empty($braintreeClientToken))
<div id="expiration_year" class="braintree-hosted form-control"></div>
@else
{!! Former::select(!empty($tokenize) ? '' : 'expiration_year')
->id('expiration_year')
->autocomplete('cc-exp-year')
->placeholder(trans('texts.expiration_year'))
->addOption('2016', '2016')
->addOption('2017', '2017')
->addOption('2018', '2018')
->addOption('2019', '2019')
->addOption('2020', '2020')
->addOption('2021', '2021')
->addOption('2022', '2022')
->addOption('2023', '2023')
->addOption('2024', '2024')
->addOption('2025', '2025')
->addOption('2026', '2026')->label('')
!!}
@endif
</div>
</div>
<div class="row" style="padding-top:18px">
<div class="col-md-5">
@if (isset($amount) && $client && $account->showTokenCheckbox($storageGateway/* will contain gateway id */))
<input id="token_billing" type="checkbox" name="token_billing" {{ $account->selectTokenCheckbox() ? 'CHECKED' : '' }} value="1" style="margin-left:0px; vertical-align:top">
<label for="token_billing" class="checkbox" style="display: inline;">{{ trans('texts.token_billing') }}</label>
<span class="help-block" style="font-size:15px">
@if ($storageGateway == GATEWAY_STRIPE)
{!! trans('texts.token_billing_secure', ['link' => link_to('https://stripe.com/', 'Stripe.com', ['target' => '_blank'])]) !!}
@elseif ($storageGateway == GATEWAY_BRAINTREE)
{!! trans('texts.token_billing_secure', ['link' => link_to('https://www.braintreepayments.com/', 'Braintree', ['target' => '_blank'])]) !!}
@endif
</span>
@endif
</div>
<div class="col-md-7">
@if (isset($acceptedCreditCardTypes))
<div class="pull-right">
@foreach ($acceptedCreditCardTypes as $card)
<img src="{{ $card['source'] }}" alt="{{ $card['alt'] }}" style="width: 70px; display: inline; margin-right: 6px;"/>
@endforeach
</div>
@endif
</div>
</div>
<p>&nbsp;</p>
<center>
@if(isset($amount))
{!! Button::success(strtoupper(trans('texts.pay_now') . ' - ' . $account->formatMoney($amount, $client, true) ))
->submit()
->large() !!}
@else
{!! Button::success(strtoupper(trans('texts.add_credit_card') ))
->submit()
->large() !!}
@endif
</center>
<p>&nbsp;</p>
@endif
<div id="js-error-message" style="display:none" class="alert alert-danger"></div>
</div>
</div>
</div>
<p>&nbsp;</p>
<p>&nbsp;</p>
</div>
{!! Former::close() !!}
<script type="text/javascript">
$(function() {
$('select').change(function() {
$(this).css({color:'#444444'});
});
$('#country_id').combobox();
$('#country').combobox();
$('#currency').combobox();
$('#first_name').focus();
@if($paymentType == PAYMENT_TYPE_STRIPE_ACH)
var routingNumberCache = {};
$('#routing_number, #country').on('change keypress keyup keydown paste', function(){setTimeout(function () {
var routingNumber = $('#routing_number').val().replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, '');
if (routingNumber.length != 9 || $("#country_id").val() != 840 || routingNumberCache[routingNumber] === false) {
$('#bank_name').hide();
} else if (routingNumberCache[routingNumber]) {
$('#bank_name').empty().append(routingNumberCache[routingNumber]).show();
} else {
routingNumberCache[routingNumber] = false;
$('#bank_name').hide();
$.ajax({
url:"{{ URL::to('/bank') }}/" + routingNumber,
success:function(data) {
var els = $().add(document.createTextNode(data.name + ", " + data.city + ", " + data.state));
routingNumberCache[routingNumber] = els;
// Still the same number?
if (routingNumber == $('#routing_number').val().replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, '')) {
$('#bank_name').empty().append(els).show();
}
},
error:function(xhr) {
if (xhr.status == 404) {
var els = $(document.createTextNode('{{trans('texts.unknown_bank')}}'));
;
routingNumberCache[routingNumber] = els;
// Still the same number?
if (routingNumber == $('#routing_number').val().replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, '')) {
$('#bank_name').empty().append(els).show();
}
}
}
})
}
},10)})
@endif
});
</script>
@if (isset($accountGateway) && $accountGateway->getPlaidEnabled())
<a href="https://plaid.com/products/auth/" target="_blank" style="display:none" id="secured_by_plaid"><img src="{{ URL::to('images/plaid-logowhite.svg') }}">{{ trans('texts.secured_by_plaid') }}</a>
<script src="https://cdn.plaid.com/link/stable/link-initialize.js"></script>
@endif
@stop

View File

@ -0,0 +1,11 @@
@extends('payments.payment_method')
@section('head')
@parent
@if (isset($accountGateway) && $accountGateway->getPlaidEnabled())
<a href="https://plaid.com/products/auth/" target="_blank" style="display:none" id="secured_by_plaid"><img src="{{ URL::to('images/plaid-logowhite.svg') }}">{{ trans('texts.secured_by_plaid') }}</a>
<script src="https://cdn.plaid.com/link/stable/link-initialize.js"></script>
@endif
@stop

View File

@ -0,0 +1,73 @@
@extends('payments.credit_card')
@section('head')
@parent
<script type="text/javascript" src="https://js.braintreegateway.com/js/braintree-2.23.0.min.js"></script>
<script type="text/javascript" >
$(function() {
var $form = $('.payment-form');
braintree.setup("{{ $transactionToken }}", "custom", {
id: "payment-form",
hostedFields: {
number: {
selector: "#card_number",
placeholder: "{{ trans('texts.card_number') }}"
},
cvv: {
selector: "#cvv",
placeholder: "{{ trans('texts.cvv') }}"
},
expirationMonth: {
selector: "#expiration_month",
placeholder: "{{ trans('texts.expiration_month') }}"
},
expirationYear: {
selector: "#expiration_year",
placeholder: "{{ trans('texts.expiration_year') }}"
},
styles: {
'input': {
'font-family': {!! json_encode(Utils::getFromCache($account->getBodyFontId(), 'fonts')['css_stack']) !!},
'font-weight': "{{ Utils::getFromCache($account->getBodyFontId(), 'fonts')['css_weight'] }}",
'font-size': '16px'
}
}
},
onError: function(e) {
$form.find('button').prop('disabled', false);
// Show the errors on the form
if (e.details && e.details.invalidFieldKeys.length) {
var invalidField = e.details.invalidFieldKeys[0];
if (invalidField == 'number') {
$('#js-error-message').html('{{ trans('texts.invalid_card_number') }}').fadeIn();
}
else if (invalidField == 'expirationDate' || invalidField == 'expirationYear' || invalidField == 'expirationMonth') {
$('#js-error-message').html('{{ trans('texts.invalid_expiry') }}').fadeIn();
}
else if (invalidField == 'cvv') {
$('#js-error-message').html('{{ trans('texts.invalid_cvv') }}').fadeIn();
}
}
else {
$('#js-error-message').html(e.message).fadeIn();
}
},
onPaymentMethodReceived: function(e) {
// Insert the token into the form so it gets submitted to the server
$form.append($('<input type="hidden" name="sourceToken"/>').val(e.nonce));
// and submit
$form.get(0).submit();
}
});
$('.payment-form').submit(function(event) {
var $form = $(this);
// Disable the submit button to prevent repeated clicks
$form.find('button').prop('disabled', true);
$('#js-error-message').hide();
});
});
</script>
@stop

View File

@ -0,0 +1,42 @@
@extends('payments.payment_method')
@section('payment_details')
@parent
{!! Former::open($url) !!}
<h3>{{ trans('texts.paypal') }}</h3>
<div>{{$details->firstName}} {{$details->lastName}}</div>
<div>{{$details->email}}</div>
<input type="hidden" name="sourceToken" value="{{$sourceId}}">
<input type="hidden" name="first_name" value="{{$details->firstName}}">
<input type="hidden" name="last_name" value="{{$details->lastName}}">
<input type="hidden" name="email" value="{{$details->email}}">
<p>&nbsp;</p>
@if (isset($amount) && $client && $account->showTokenCheckbox())
<input id="token_billing" type="checkbox" name="token_billing" {{ $account->selectTokenCheckbox() ? 'CHECKED' : '' }} value="1" style="margin-left:0px; vertical-align:top">
<label for="token_billing" class="checkbox" style="display: inline;">{{ trans('texts.token_billing_braintree_paypal') }}</label>
<span class="help-block" style="font-size:15px">
{!! trans('texts.token_billing_secure', ['link' => link_to('https://www.braintreepayments.com/', 'Braintree', ['target' => '_blank'])]) !!}
</span>
@endif
<p>&nbsp;</p>
<center>
@if(isset($amount))
{!! Button::success(strtoupper(trans('texts.pay_now') . ' - ' . $account->formatMoney($amount, $client, true) ))
->submit()
->large() !!}
@else
{!! Button::success(strtoupper(trans('texts.add_paypal_account') ))
->submit()
->large() !!}
@endif
</center>
@stop

View File

@ -1,4 +1,4 @@
@if ($checkoutComDebug)
@if ($accountGateway->getConfigField('testMode'))
<script src="https://sandbox.checkout.com/js/v1/checkout.js"></script>
@else
<script src="https://cdn.checkout.com/js/checkout.js"></script>
@ -7,9 +7,9 @@
<form method="POST" class="payment-form">
<script>
Checkout.render({
debugMode: {{ $checkoutComDebug ? 'true' : 'false' }},
publicKey: '{{ $checkoutComKey }}',
paymentToken: '{{ $checkoutComToken }}',
debugMode: {{ $accountGateway->getConfigField('testMode') ? 'true' : 'false' }},
publicKey: '{{ $accountGateway->getConfigField('publicApiKey') }}',
paymentToken: '{{ $transactionToken }}',
customerEmail: '{{ $contact->email }}',
customerName: '{{ $contact->getFullName() }}',
value: {{ $invoice->getRequestedAmount() * 100 }},
@ -19,7 +19,7 @@
themeColor: '#3075dd',
buttonColor:'#51c470',
cardCharged: function(event){
location.href = '{{ URL::to('/complete?token=' . $checkoutComToken) }}';
location.href = '{{ URL::to('/complete/'. $invitation->invitation_key . '/credit_card?token=' . $transactionToken) }}';
}
});
</script>

View File

@ -0,0 +1,259 @@
@extends('payments.payment_method')
@section('payment_details')
{!! Former::vertical_open($url)
->autocomplete('on')
->addClass('payment-form')
->id('payment-form')
->rules(array(
'first_name' => 'required',
'last_name' => 'required',
'card_number' => 'required',
'expiration_month' => 'required',
'expiration_year' => 'required',
'cvv' => 'required',
'address1' => 'required',
'city' => 'required',
'state' => 'required',
'postal_code' => 'required',
'country_id' => 'required',
'phone' => 'required',
'email' => 'required|email',
'authorize_ach' => 'required',
'tos_agree' => 'required',
'account_number' => 'required',
'routing_number' => 'required',
'account_holder_name' => 'required',
'account_holder_type' => 'required',
)) !!}
@if ($client)
{{ Former::populate($client) }}
{{ Former::populateField('first_name', $contact->first_name) }}
{{ Former::populateField('last_name', $contact->last_name) }}
{{ Former::populateField('email', $contact->email) }}
@if (!$client->country_id && $client->account->country_id)
{{ Former::populateField('country_id', $client->account->country_id) }}
@endif
@if (!$client->currency_id && $client->account->currency_id)
{{ Former::populateField('currency_id', $client->account->currency_id) }}
{{ Former::populateField('currency', $client->account->currency->code) }}
@endif
@endif
@if (Utils::isNinjaDev())
{{ Former::populateField('first_name', 'Test') }}
{{ Former::populateField('last_name', 'Test') }}
{{ Former::populateField('address1', '350 5th Ave') }}
{{ Former::populateField('city', 'New York') }}
{{ Former::populateField('state', 'NY') }}
{{ Former::populateField('postal_code', '10118') }}
{{ Former::populateField('country_id', 840) }}
<script>
$(function() {
$('#card_number').val('4242424242424242');
$('#cvv').val('1234');
$('#expiration_month').val(1);
$('#expiration_year').val({{ date_create()->modify('+3 year')->format('Y') }});
})
</script>
@endif
<h3>{{ trans('texts.contact_information') }}</h3>
<div class="row">
<div class="col-md-6">
{!! Former::text('first_name')
->placeholder(trans('texts.first_name'))
->label('') !!}
</div>
<div class="col-md-6">
{!! Former::text('last_name')
->placeholder(trans('texts.last_name'))
->autocomplete('family-name')
->label('') !!}
</div>
</div>
@if (isset($paymentTitle) || ! empty($contact->email))
<div class="row" style="display:{{ isset($paymentTitle) ? 'block' : 'none' }}">
<div class="col-md-12">
{!! Former::text('email')
->placeholder(trans('texts.email'))
->autocomplete('email')
->label('') !!}
</div>
</div>
@endif
<p>&nbsp;<br/>&nbsp;</p>
@if (!empty($showAddress))
<h3>{{ trans('texts.billing_address') }}&nbsp;<span class="help">{{ trans('texts.payment_footer1') }}</span></h3>
<div class="row">
<div class="col-md-6">
{!! Former::text('address1')
->autocomplete('address-line1')
->placeholder(trans('texts.address1'))
->label('') !!}
</div>
<div class="col-md-6">
{!! Former::text('address2')
->autocomplete('address-line2')
->placeholder(trans('texts.address2'))
->label('') !!}
</div>
</div>
<div class="row">
<div class="col-md-6">
{!! Former::text('city')
->autocomplete('address-level2')
->placeholder(trans('texts.city'))
->label('') !!}
</div>
<div class="col-md-6">
{!! Former::text('state')
->autocomplete('address-level1')
->placeholder(trans('texts.state'))
->label('') !!}
</div>
</div>
<div class="row">
<div class="col-md-6">
{!! Former::text('postal_code')
->autocomplete('postal-code')
->placeholder(trans('texts.postal_code'))
->label('') !!}
</div>
<div class="col-md-6">
{!! Former::select('country_id')
->placeholder(trans('texts.country_id'))
->fromQuery(Cache::get('countries'), 'name', 'id')
->addGroupClass('country-select')
->label('') !!}
</div>
</div>
<p>&nbsp;<br/>&nbsp;</p>
@endif
<h3>{{ trans('texts.billing_method') }}</h3>
<div class="row">
<div class="col-md-9">
@if ($accountGateway->gateway_id == GATEWAY_BRAINTREE)
<div id="card_number" class="braintree-hosted form-control"></div>
@else
{!! Former::text(!empty($tokenize) ? '' : 'card_number')
->id('card_number')
->placeholder(trans('texts.card_number'))
->autocomplete('cc-number')
->label('') !!}
@endif
</div>
<div class="col-md-3">
@if ($accountGateway->gateway_id == GATEWAY_BRAINTREE)
<div id="cvv" class="braintree-hosted form-control"></div>
@else
{!! Former::text(!empty($tokenize) ? '' : 'cvv')
->id('cvv')
->placeholder(trans('texts.cvv'))
->autocomplete('off')
->label('') !!}
@endif
</div>
</div>
<div class="row">
<div class="col-md-6">
@if ($accountGateway->gateway_id == GATEWAY_BRAINTREE)
<div id="expiration_month" class="braintree-hosted form-control"></div>
@else
{!! Former::select(!empty($tokenize) ? '' : 'expiration_month')
->id('expiration_month')
->autocomplete('cc-exp-month')
->placeholder(trans('texts.expiration_month'))
->addOption('01 - January', '1')
->addOption('02 - February', '2')
->addOption('03 - March', '3')
->addOption('04 - April', '4')
->addOption('05 - May', '5')
->addOption('06 - June', '6')
->addOption('07 - July', '7')
->addOption('08 - August', '8')
->addOption('09 - September', '9')
->addOption('10 - October', '10')
->addOption('11 - November', '11')
->addOption('12 - December', '12')->label('')
!!}
@endif
</div>
<div class="col-md-6">
@if ($accountGateway->gateway_id == GATEWAY_BRAINTREE)
<div id="expiration_year" class="braintree-hosted form-control"></div>
@else
{!! Former::select(!empty($tokenize) ? '' : 'expiration_year')
->id('expiration_year')
->autocomplete('cc-exp-year')
->placeholder(trans('texts.expiration_year'))
->addOption('2016', '2016')
->addOption('2017', '2017')
->addOption('2018', '2018')
->addOption('2019', '2019')
->addOption('2020', '2020')
->addOption('2021', '2021')
->addOption('2022', '2022')
->addOption('2023', '2023')
->addOption('2024', '2024')
->addOption('2025', '2025')
->addOption('2026', '2026')->label('')
!!}
@endif
</div>
</div>
<div class="row" style="padding-top:18px">
<div class="col-md-5">
@if (isset($amount) && $client && $account->showTokenCheckbox($storageGateway/* will contain gateway id */))
<input id="token_billing" type="checkbox" name="token_billing" {{ $account->selectTokenCheckbox() ? 'CHECKED' : '' }} value="1" style="margin-left:0px; vertical-align:top">
<label for="token_billing" class="checkbox" style="display: inline;">{{ trans('texts.token_billing') }}</label>
<span class="help-block" style="font-size:15px">
@if ($storageGateway == GATEWAY_STRIPE)
{!! trans('texts.token_billing_secure', ['link' => link_to('https://stripe.com/', 'Stripe.com', ['target' => '_blank'])]) !!}
@elseif ($storageGateway == GATEWAY_BRAINTREE)
{!! trans('texts.token_billing_secure', ['link' => link_to('https://www.braintreepayments.com/', 'Braintree', ['target' => '_blank'])]) !!}
@endif
</span>
@endif
</div>
<div class="col-md-7">
@if (isset($acceptedCreditCardTypes))
<div class="pull-right">
@foreach ($acceptedCreditCardTypes as $card)
<img src="{{ $card['source'] }}" alt="{{ $card['alt'] }}" style="width: 70px; display: inline; margin-right: 6px;"/>
@endforeach
</div>
@endif
</div>
</div>
<div class="col-md-12">
<div id="js-error-message" style="display:none" class="alert alert-danger"></div>
</div>
<p>&nbsp;</p>
<center>
@if(isset($amount))
{!! Button::success(strtoupper(trans('texts.pay_now') . ' - ' . $account->formatMoney($amount, $client, true) ))
->submit()
->large() !!}
@else
{!! Button::success(strtoupper(trans('texts.add_credit_card') ))
->submit()
->large() !!}
@endif
</center>
<p>&nbsp;</p>
@stop

View File

@ -22,7 +22,7 @@ body {
@endif
width: 100%;
padding: 11px;
color: #8c8c8c;
color: #444444;
background: #f9f9f9;
border: 1px solid #ebe7e7;
border-radius: 3px;

View File

@ -0,0 +1,74 @@
@extends('public.header')
@section('content')
@include('payments.payment_css')
<div class="container">
<p>&nbsp;</p>
<div class="panel panel-default">
<div class="panel-body">
<div class="row">
<div class="col-md-7">
<header>
@if ($client && isset($invoiceNumber))
<h2>{{ $client->getDisplayName() }}</h2>
<h3>{{ trans('texts.invoice') . ' ' . $invoiceNumber }}<span>|&nbsp; {{ trans('texts.amount_due') }}: <em>{{ $account->formatMoney($amount, $client, true) }}</em></span></h3>
@elseif ($paymentTitle)
<h2>{{ $paymentTitle }}
@if(isset($paymentSubtitle))
<br/><small>{{ $paymentSubtitle }}</small>
@endif
</h2>
@endif
</header>
</div>
<div class="col-md-5">
@if (Request::secure() || Utils::isNinjaDev())
<div class="secure">
<h3>{{ trans('texts.secure_payment') }}</h3>
<div>{{ trans('texts.256_encryption') }}</div>
</div>
@endif
</div>
</div>
<p>&nbsp;<br/>&nbsp;</p>
<div>
@yield('payment_details')
</div>
</div>
</div>
</div>
<p>&nbsp;</p>
<p>&nbsp;</p>
</div>
{!! Former::close() !!}
<script type="text/javascript">
$(function() {
$('select').change(function() {
$(this).css({color:'#444444'});
});
$('#country_id').combobox();
$('#currency_id').combobox();
$('#first_name').focus();
});
</script>
@stop

View File

@ -16,6 +16,7 @@
display:inline-block;
}
</style>
@if (!empty($braintreeClientToken))
<script type="text/javascript" src="https://js.braintreegateway.com/js/braintree-2.23.0.min.js"></script>
<script type="text/javascript" >
@ -75,18 +76,22 @@
});
</script>
@endif
@if(!empty($paymentMethods))
@if(!empty($paymentMethods) && count($paymentMethods))
<h3>{{ trans('texts.payment_methods') }}</h3>
@foreach ($paymentMethods as $paymentMethod)
<div class="payment_method">
<span class="payment_method_img_container">
<img height="22" src="{{URL::to('/images/credit_cards/'.str_replace(' ', '', strtolower($paymentMethod->payment_type->name).'.png'))}}" alt="{{trans("texts.card_" . str_replace(' ', '', strtolower($paymentMethod->payment_type->name)))}}">
<img height="22" src="{{ $paymentMethod->imageUrl() }}"
alt="{{ trans("texts.card_" . str_replace(' ', '', strtolower($paymentMethod->payment_type->name))) }}">
</span>
@if(!empty($paymentMethod->last4))
<span class="payment_method_number">&bull;&bull;&bull;&bull;&bull;{{$paymentMethod->last4}}</span>
@endif
@if($paymentMethod->payment_type_id == PAYMENT_TYPE_ACH)
@if($paymentMethod->bank_name)
{{ $paymentMethod->bank_name }}
@else
{{ trans('texts.bank_account') }}
@endif
@if($paymentMethod->status == PAYMENT_METHOD_STATUS_NEW)
@if($gateway->gateway_id == GATEWAY_STRIPE)
@ -97,50 +102,58 @@
@elseif($paymentMethod->status == PAYMENT_METHOD_STATUS_VERIFICATION_FAILED)
({{trans('texts.verification_failed')}})
@endif
@elseif($paymentMethod->payment_type_id == PAYMENT_TYPE_ID_PAYPAL)
{{ $paymentMethod->email }}
@elseif($paymentMethod->expiration)
{!! trans('texts.card_expiration', array('expires'=>Utils::fromSqlDate($paymentMethod->expiration, false)->format('m/y'))) !!}
@elseif($paymentMethod->payment_type_id == PAYMENT_TYPE_PAYPAL)
{{ trans('texts.paypal') . ': ' . $paymentMethod->email }}
@else
{{ trans('texts.credit_card') }}
@endif
@if($paymentMethod->id == $paymentMethod->account_gateway_token->default_payment_method_id)
({{trans('texts.used_for_auto_bill')}})
@elseif($paymentMethod->payment_type_id != PAYMENT_TYPE_ACH || $paymentMethod->status == PAYMENT_METHOD_STATUS_VERIFIED)
<a href="#" onclick="setDefault('{{$paymentMethod->public_id}}')">({{trans('texts.use_for_auto_bill')}})</a>
@endif
<a href="#" class="payment_method_remove" onclick="removePaymentMethod('{{$paymentMethod->public_id}}')">&times;</a>
<a href="#" title="{{ trans('texts.remove') }}" class="payment_method_remove" onclick="removePaymentMethod('{{$paymentMethod->public_id}}')">&times;</a>
</div>
@endforeach
@endif
@if($gateway->gateway_id != GATEWAY_STRIPE || $gateway->getPublishableStripeKey())
<center>
@if (false && $account->getGatewayByType(GATEWAY_TYPE_CREDIT_CARD) && $account->getGatewayByType(GATEWAY_TYPE_TOKEN))
{!! Button::success(strtoupper(trans('texts.add_credit_card')))
->asLinkTo(URL::to('/client/paymentmethods/add/'.($gateway->getPaymentType() == PAYMENT_TYPE_STRIPE ? 'stripe_credit_card' : 'credit_card'))) !!}
@if($gateway->getACHEnabled())
->asLinkTo(URL::to('/client/add/credit_card')) !!}
&nbsp;
@if($gateway->gateway_id == GATEWAY_STRIPE)
{!! Button::success(strtoupper(trans('texts.add_bank_account')))
->asLinkTo(URL::to('/client/paymentmethods/add/stripe_ach')) !!}
@elseif($gateway->gateway_id == GATEWAY_WEPAY)
@endif
@if (false && $account->getGatewayByType(GATEWAY_TYPE_BANK_TRANSFER) && $account->getGatewayByType(GATEWAY_TYPE_TOKEN))
{!! Button::success(strtoupper(trans('texts.add_bank_account')))
->withAttributes(['id'=>'add-ach'])
->asLinkTo(URL::to('/client/paymentmethods/add/wepay_ach')) !!}
@endif
@endif
@if($gateway->getPayPalEnabled())
->asLinkTo(URL::to('/client/add/bank_transfer')) !!}
&nbsp;
@endif
@if (false && $account->getGatewayByType(GATEWAY_TYPE_PAYPAL) && $account->getGatewayByType(GATEWAY_TYPE_TOKEN))
{!! Button::success(strtoupper(trans('texts.add_paypal_account')))
->withAttributes(['id'=>'add-paypal'])
->asLinkTo(URL::to('/client/paymentmethods/add/braintree_paypal')) !!}
->asLinkTo(URL::to('/client/add/paypal')) !!}
<div id="paypal-container"></div>
@endif
</center>
@endif
<div class="modal fade" id="completeVerificationModal" tabindex="-1" role="dialog" aria-labelledby="completeVerificationModalLabel" aria-hidden="true">
<div class="modal-dialog" style="min-width:150px">
<div class="modal-content">
{!! Former::open('/client/paymentmethods/verify') !!}
{!! Former::open('/client/payment_methods/verify') !!}
@if (Utils::isNinjaDev())
<script>
$(function() {
$('#verification1').val('32');
$('#verification2').val('45');
})
</script>
@endif
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
<h4 class="modal-title" id="completeVerificationModalLabel">{{ trans('texts.complete_verification') }}</h4>
@ -199,7 +212,8 @@
</div>
</div>
</div>
{!! Former::open(URL::to('/client/paymentmethods/default'))->id('defaultSourceForm') !!}
{!! Former::open(URL::to('/client/payment_methods/default'))->id('defaultSourceForm') !!}
<input type="hidden" name="source" id="default_id">
{!! Former::close() !!}
@ -212,7 +226,7 @@
}
function removePaymentMethod(sourceId) {
$('#removeForm').attr('action', '{{ URL::to('/client/paymentmethods/%s/remove') }}'.replace('%s', sourceId))
$('#removeForm').attr('action', '{{ URL::to('/client/payment_methods/%s/remove') }}'.replace('%s', sourceId))
$('#removePaymentMethodModal').modal('show');
}

View File

@ -0,0 +1,285 @@
@extends('payments.bank_transfer')
@section('head')
@parent
<script type="text/javascript" src="https://js.stripe.com/v2/"></script>
<script type="text/javascript">
Stripe.setPublishableKey('{{ $accountGateway->getPublishableStripeKey() }}');
$(function() {
var countries = {!! Cache::get('countries')->pluck('iso_3166_2','id') !!};
$('.payment-form').submit(function(event) {
if($('[name=plaidAccountId]').length)return;
var $form = $(this);
var data = {
account_holder_name: $('#account_holder_name').val(),
account_holder_type: $('[name=account_holder_type]:checked').val(),
currency: $("#currency_id").val(),
country: countries[$("#country_id").val()],
routing_number: $('#routing_number').val().replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, ''),
account_number: $('#account_number').val().replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, '')
};
// Validate the account details
if (!data.account_holder_type) {
$('#js-error-message').html('{{ trans('texts.missing_account_holder_type') }}').fadeIn();
return false;
}
if (!data.account_holder_name) {
$('#js-error-message').html('{{ trans('texts.missing_account_holder_name') }}').fadeIn();
return false;
}
if (!data.routing_number || !Stripe.bankAccount.validateRoutingNumber(data.routing_number, data.country)) {
$('#js-error-message').html('{{ trans('texts.invalid_routing_number') }}').fadeIn();
return false;
}
if (data.account_number != $('#confirm_account_number').val().replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, '')) {
$('#js-error-message').html('{{ trans('texts.account_number_mismatch') }}').fadeIn();
return false;
}
if (!data.account_number || !Stripe.bankAccount.validateAccountNumber(data.account_number, data.country)) {
$('#js-error-message').html('{{ trans('texts.invalid_account_number') }}').fadeIn();
return false;
}
// Disable the submit button to prevent repeated clicks
$form.find('button').prop('disabled', true);
$('#js-error-message').hide();
Stripe.bankAccount.createToken(data, stripeResponseHandler);
// Prevent the form from submitting with the default action
return false;
});
@if ($accountGateway->getPlaidEnabled())
var plaidHandler = Plaid.create({
selectAccount: true,
env: '{{ $accountGateway->getPlaidEnvironment() }}',
clientName: {!! json_encode($account->getDisplayName()) !!},
key: '{{ $accountGateway->getPlaidPublicKey() }}',
product: 'auth',
onSuccess: plaidSuccessHandler,
onExit : function(){$('#secured_by_plaid').hide()}
});
$('#plaid_link_button').click(function(){plaidHandler.open();$('#secured_by_plaid').fadeIn()});
$('#plaid_unlink').click(function(e){
e.preventDefault();
$('#manual_container').fadeIn();
$('#plaid_linked').hide();
$('#plaid_link_button').show();
$('#pay_now_button').hide();
$('#add_account_button').show();
$('[name=plaidPublicToken]').remove();
$('[name=plaidAccountId]').remove();
$('[name=account_holder_type],#account_holder_name').attr('required','required');
})
@endif
});
function stripeResponseHandler(status, response) {
var $form = $('.payment-form');
if (response.error) {
// Show the errors on the form
var error = response.error.message;
if(response.error.param == 'bank_account[country]') {
error = "{{trans('texts.country_not_supported')}}";
}
$form.find('button').prop('disabled', false);
$('#js-error-message').html(error).fadeIn();
} else {
// response contains id and card, which contains additional card details
var token = response.id;
// Insert the token into the form so it gets submitted to the server
$form.append($('<input type="hidden" name="sourceToken"/>').val(token));
// and submit
$form.get(0).submit();
}
};
function plaidSuccessHandler(public_token, metadata) {
$('#secured_by_plaid').hide()
var $form = $('.payment-form');
$form.append($('<input type="hidden" name="plaidPublicToken"/>').val(public_token));
$form.append($('<input type="hidden" name="plaidAccountId"/>').val(metadata.account_id));
$('#plaid_linked_status').text('{{ trans('texts.plaid_linked_status') }}'.replace(':bank', metadata.institution.name));
$('#manual_container').fadeOut();
$('#plaid_linked').show();
$('#plaid_link_button').hide();
$('[name=account_holder_type],#account_holder_name').removeAttr('required');
var payNowBtn = $('#pay_now_button');
if(payNowBtn.length) {
payNowBtn.show();
$('#add_account_button').hide();
}
};
</script>
@stop
@section('payment_details')
@if ($accountGateway->getPlaidEnabled())
<div id="plaid_container">
<a class="btn btn-default btn-lg" id="plaid_link_button">
<img src="{{ URL::to('images/plaid-logo.svg') }}">
<img src="{{ URL::to('images/plaid-logowhite.svg') }}" class="hoverimg">
{{ trans('texts.link_with_plaid') }}
</a>
<div id="plaid_linked">
<div id="plaid_linked_status"></div>
<a href="#" id="plaid_unlink">{{ trans('texts.unlink') }}</a>
</div>
</div>
@endif
<div id="manual_container">
@if($accountGateway->getPlaidEnabled())
<div id="plaid_or"><span>{{ trans('texts.or') }}</span></div>
<h4>{{ trans('texts.link_manually') }}</h4>
@endif
<p>{{ trans('texts.ach_verification_delay_help') }}</p><br/>
{!! Former::open($url)
->autocomplete('on')
->addClass('payment-form')
->id('payment-form')
->rules(array(
'country_id' => 'required',
'currency_id' => 'required',
'account_number' => 'required',
'routing_number' => 'required',
'account_holder_name' => 'required',
'account_holder_type' => 'required',
'authorize_ach' => 'required',
)) !!}
{!! Former::populateField('account_holder_type', 'individual') !!}
{!! Former::populateField('country_id', $client->country_id) !!}
{!! Former::populateField('currency_id', $client->getCurrencyCode()) !!}
@if (Utils::isNinjaDev())
{!! Former::populateField('account_holder_name', 'Test Client') !!}
<script>
$(function() {
$('#routing_number').val('110000000');
$('#account_number').val('000123456789');
$('#confirm_account_number').val('000123456789');
$('#authorize_ach').prop('checked', true);
})
</script>
@endif
{!! Former::radios('account_holder_type')->radios(array(
trans('texts.individual_account') => array('value' => 'individual'),
trans('texts.company_account') => array('value' => 'company'),
))->inline()->label(trans('texts.account_holder_type')); !!}
{!! Former::text('account_holder_name')
->label(trans('texts.account_holder_name')) !!}
{!! Former::select('country_id')
->label(trans('texts.country_id'))
->addOption('','')
->fromQuery(Cache::get('countries'), 'name', 'id')
->addGroupClass('country-select') !!}
{!! Former::select('currency_id')
->label(trans('texts.currency_id'))
->addOption('','')
->fromQuery(Cache::get('currencies'), 'name', 'code')
->addGroupClass('currency-select') !!}
{!! Former::text('')
->id('routing_number')
->label(trans('texts.routing_number')) !!}
<div class="form-group" style="margin-top:-15px">
<div class="col-md-8 col-md-offset-4">
<div id="bank_name"></div>
</div>
</div>
{!! Former::text('')
->id('account_number')
->label(trans('texts.account_number')) !!}
{!! Former::text('')
->id('confirm_account_number')
->label(trans('texts.confirm_account_number')) !!}
</div>
{!! Former::checkbox('authorize_ach')
->text(trans('texts.ach_authorization', ['company'=>$account->getDisplayName(), 'email' => $account->work_email]))
->label(' ') !!}
<div class="col-md-12">
<div id="js-error-message" style="display:none" class="alert alert-danger"></div>
</div>
<p>&nbsp;</p>
<div class="col-md-8 col-md-offset-4">
{!! Button::success(strtoupper(trans('texts.add_account')))
->submit()
->withAttributes(['id'=>'add_account_button'])
->large() !!}
@if($accountGateway->getPlaidEnabled() && !empty($amount))
{!! Button::success(strtoupper(trans('texts.pay_now') . ' - ' . $account->formatMoney($amount, $client, true) ))
->submit()
->withAttributes(['style'=>'display:none', 'id'=>'pay_now_button'])
->large() !!}
@endif
</div>
<script type="text/javascript">
var routingNumberCache = {};
$('#routing_number, #country').on('change keypress keyup keydown paste', function(){setTimeout(function () {
var routingNumber = $('#routing_number').val().replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, '');
if (routingNumber.length != 9 || $("#country_id").val() != 840 || routingNumberCache[routingNumber] === false) {
$('#bank_name').hide();
} else if (routingNumberCache[routingNumber]) {
$('#bank_name').empty().append(routingNumberCache[routingNumber]).show();
} else {
routingNumberCache[routingNumber] = false;
$('#bank_name').hide();
$.ajax({
url:"{{ URL::to('/bank') }}/" + routingNumber,
success:function(data) {
var els = $().add(document.createTextNode(data.name + ", " + data.city + ", " + data.state));
routingNumberCache[routingNumber] = els;
// Still the same number?
if (routingNumber == $('#routing_number').val().replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, '')) {
$('#bank_name').empty().append(els).show();
}
},
error:function(xhr) {
if (xhr.status == 404) {
var els = $(document.createTextNode('{{trans('texts.unknown_bank')}}'));
routingNumberCache[routingNumber] = els;
// Still the same number?
if (routingNumber == $('#routing_number').val().replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, '')) {
$('#bank_name').empty().append(els).show();
}
}
}
})
}
},10)})
</script>
@stop

View File

@ -0,0 +1,89 @@
@extends('payments.credit_card')
@section('head')
@parent
@if ($accountGateway->getPublishableStripeKey())
<script type="text/javascript" src="https://js.stripe.com/v2/"></script>
<script type="text/javascript">
Stripe.setPublishableKey('{{ $accountGateway->getPublishableStripeKey() }}');
$(function() {
var countries = {!! Cache::get('countries')->pluck('iso_3166_2','id') !!};
$('.payment-form').submit(function(event) {
if($('[name=plaidAccountId]').length)return;
var $form = $(this);
var data = {
name: $('#first_name').val() + ' ' + $('#last_name').val(),
address_line1: $('#address1').val(),
address_line2: $('#address2').val(),
address_city: $('#city').val(),
address_state: $('#state').val(),
address_zip: $('#postal_code').val(),
address_country: $("#country_id option:selected").text(),
number: $('#card_number').val(),
cvc: $('#cvv').val(),
exp_month: $('#expiration_month').val(),
exp_year: $('#expiration_year').val()
};
// Validate the card details
if (!Stripe.card.validateCardNumber(data.number)) {
$('#js-error-message').html('{{ trans('texts.invalid_card_number') }}').fadeIn();
return false;
}
if (!Stripe.card.validateExpiry(data.exp_month, data.exp_year)) {
$('#js-error-message').html('{{ trans('texts.invalid_expiry') }}').fadeIn();
return false;
}
if (!Stripe.card.validateCVC(data.cvc)) {
$('#js-error-message').html('{{ trans('texts.invalid_cvv') }}').fadeIn();
return false;
}
// Disable the submit button to prevent repeated clicks
$form.find('button').prop('disabled', true);
$('#js-error-message').hide();
Stripe.card.createToken(data, stripeResponseHandler);
// Prevent the form from submitting with the default action
return false;
});
});
function stripeResponseHandler(status, response) {
var $form = $('.payment-form');
if (response.error) {
// Show the errors on the form
var error = response.error.message;
$form.find('button').prop('disabled', false);
$('#js-error-message').html(error).fadeIn();
} else {
// response contains id and card, which contains additional card details
var token = response.id;
// Insert the token into the form so it gets submitted to the server
$form.append($('<input type="hidden" name="sourceToken"/>').val(token));
// and submit
$form.get(0).submit();
}
};
</script>
@else
<script type="text/javascript">
$(function() {
$('.payment-form').submit(function(event) {
var $form = $(this);
// Disable the submit button to prevent repeated clicks
$form.find('button').prop('disabled', true);
return true;
});
});
</script>
@endif
@stop

View File

@ -1,67 +0,0 @@
<script type="text/javascript" src="https://js.braintreegateway.com/js/braintree-2.23.0.min.js"></script>
<script type="text/javascript" >
$(function() {
var $form = $('.payment-form');
braintree.setup("{{ $braintreeClientToken }}", "custom", {
id: "payment-form",
hostedFields: {
number: {
selector: "#card_number",
placeholder: "{{ trans('texts.card_number') }}"
},
cvv: {
selector: "#cvv",
placeholder: "{{ trans('texts.cvv') }}"
},
expirationMonth: {
selector: "#expiration_month",
placeholder: "{{ trans('texts.expiration_month') }}"
},
expirationYear: {
selector: "#expiration_year",
placeholder: "{{ trans('texts.expiration_year') }}"
},
styles: {
'input': {
'font-family': {!! json_encode(Utils::getFromCache($account->getBodyFontId(), 'fonts')['css_stack']) !!},
'font-weight': "{{ Utils::getFromCache($account->getBodyFontId(), 'fonts')['css_weight'] }}",
'font-size': '16px'
}
}
},
onError: function(e) {
$form.find('button').prop('disabled', false);
// Show the errors on the form
if (e.details && e.details.invalidFieldKeys.length) {
var invalidField = e.details.invalidFieldKeys[0];
if (invalidField == 'number') {
$('#js-error-message').html('{{ trans('texts.invalid_card_number') }}').fadeIn();
}
else if (invalidField == 'expirationDate' || invalidField == 'expirationYear' || invalidField == 'expirationMonth') {
$('#js-error-message').html('{{ trans('texts.invalid_expiry') }}').fadeIn();
}
else if (invalidField == 'cvv') {
$('#js-error-message').html('{{ trans('texts.invalid_cvv') }}').fadeIn();
}
}
else {
$('#js-error-message').html(e.message).fadeIn();
}
},
onPaymentMethodReceived: function(e) {
// Insert the token into the form so it gets submitted to the server
$form.append($('<input type="hidden" name="sourceToken"/>').val(e.nonce));
// and submit
$form.get(0).submit();
}
});
$('.payment-form').submit(function(event) {
var $form = $(this);
// Disable the submit button to prevent repeated clicks
$form.find('button').prop('disabled', true);
$('#js-error-message').hide();
});
});
</script>

View File

@ -1,154 +0,0 @@
<script type="text/javascript" src="https://js.stripe.com/v2/"></script>
<script type="text/javascript">
Stripe.setPublishableKey('{{ $accountGateway->getPublishableStripeKey() }}');
$(function() {
var countries = {!! $countries->pluck('iso_3166_2','id') !!};
$('.payment-form').submit(function(event) {
if($('[name=plaidAccountId]').length)return;
var $form = $(this);
var data = {
@if($paymentType == PAYMENT_TYPE_STRIPE_ACH)
account_holder_name: $('#account_holder_name').val(),
account_holder_type: $('[name=account_holder_type]:checked').val(),
currency: $("#currency").val(),
country: countries[$("#country_id").val()],
routing_number: $('#routing_number').val().replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, ''),
account_number: $('#account_number').val().replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, '')
@else
name: $('#first_name').val() + ' ' + $('#last_name').val(),
address_line1: $('#address1').val(),
address_line2: $('#address2').val(),
address_city: $('#city').val(),
address_state: $('#state').val(),
address_zip: $('#postal_code').val(),
address_country: $("#country_id option:selected").text(),
number: $('#card_number').val(),
cvc: $('#cvv').val(),
exp_month: $('#expiration_month').val(),
exp_year: $('#expiration_year').val()
@endif
};
@if($paymentType == PAYMENT_TYPE_STRIPE_ACH)
// Validate the account details
if (!data.account_holder_type) {
$('#js-error-message').html('{{ trans('texts.missing_account_holder_type') }}').fadeIn();
return false;
}
if (!data.account_holder_name) {
$('#js-error-message').html('{{ trans('texts.missing_account_holder_name') }}').fadeIn();
return false;
}
if (!data.routing_number || !Stripe.bankAccount.validateRoutingNumber(data.routing_number, data.country)) {
$('#js-error-message').html('{{ trans('texts.invalid_routing_number') }}').fadeIn();
return false;
}
if (data.account_number != $('#confirm_account_number').val().replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, '')) {
$('#js-error-message').html('{{ trans('texts.account_number_mismatch') }}').fadeIn();
return false;
}
if (!data.account_number || !Stripe.bankAccount.validateAccountNumber(data.account_number, data.country)) {
$('#js-error-message').html('{{ trans('texts.invalid_account_number') }}').fadeIn();
return false;
}
@else
// Validate the card details
if (!Stripe.card.validateCardNumber(data.number)) {
$('#js-error-message').html('{{ trans('texts.invalid_card_number') }}').fadeIn();
return false;
}
if (!Stripe.card.validateExpiry(data.exp_month, data.exp_year)) {
$('#js-error-message').html('{{ trans('texts.invalid_expiry') }}').fadeIn();
return false;
}
if (!Stripe.card.validateCVC(data.cvc)) {
$('#js-error-message').html('{{ trans('texts.invalid_cvv') }}').fadeIn();
return false;
}
@endif
// Disable the submit button to prevent repeated clicks
$form.find('button').prop('disabled', true);
$('#js-error-message').hide();
@if($paymentType == PAYMENT_TYPE_STRIPE_ACH)
Stripe.bankAccount.createToken(data, stripeResponseHandler);
@else
Stripe.card.createToken(data, stripeResponseHandler);
@endif
// Prevent the form from submitting with the default action
return false;
});
@if($accountGateway->getPlaidEnabled())
var plaidHandler = Plaid.create({
selectAccount: true,
env: '{{ $accountGateway->getPlaidEnvironment() }}',
clientName: {!! json_encode($account->getDisplayName()) !!},
key: '{{ $accountGateway->getPlaidPublicKey() }}',
product: 'auth',
onSuccess: plaidSuccessHandler,
onExit : function(){$('#secured_by_plaid').hide()}
});
$('#plaid_link_button').click(function(){plaidHandler.open();$('#secured_by_plaid').fadeIn()});
$('#plaid_unlink').click(function(e){
e.preventDefault();
$('#manual_container').fadeIn();
$('#plaid_linked').hide();
$('#plaid_link_button').show();
$('#pay_now_button').hide();
$('#add_account_button').show();
$('[name=plaidPublicToken]').remove();
$('[name=plaidAccountId]').remove();
$('[name=account_holder_type],#account_holder_name').attr('required','required');
})
@endif
});
function stripeResponseHandler(status, response) {
var $form = $('.payment-form');
if (response.error) {
// Show the errors on the form
var error = response.error.message;
@if($paymentType == PAYMENT_TYPE_STRIPE_ACH)
if(response.error.param == 'bank_account[country]') {
error = "{{trans('texts.country_not_supported')}}";
}
@endif
$form.find('button').prop('disabled', false);
$('#js-error-message').html(error).fadeIn();
} else {
// response contains id and card, which contains additional card details
var token = response.id;
// Insert the token into the form so it gets submitted to the server
$form.append($('<input type="hidden" name="sourceToken"/>').val(token));
// and submit
$form.get(0).submit();
}
};
function plaidSuccessHandler(public_token, metadata) {
$('#secured_by_plaid').hide()
var $form = $('.payment-form');
$form.append($('<input type="hidden" name="plaidPublicToken"/>').val(public_token));
$form.append($('<input type="hidden" name="plaidAccountId"/>').val(metadata.account_id));
$('#plaid_linked_status').text('{{ trans('texts.plaid_linked_status') }}'.replace(':bank', metadata.institution.name));
$('#manual_container').fadeOut();
$('#plaid_linked').show();
$('#plaid_link_button').hide();
$('[name=account_holder_type],#account_holder_name').removeAttr('required');
var payNowBtn = $('#pay_now_button');
if(payNowBtn.length) {
payNowBtn.show();
$('#add_account_button').hide();
}
};
</script>

View File

@ -0,0 +1,40 @@
@extends('payments.bank_transfer')
@section('payment_details')
<h3>{{ trans('texts.bank_account') }}</h3>
@if (!empty($details))
<div>{{$details->bank_account_name}}</div>
<div>&bull;&bull;&bull;&bull;&bull;{{$details->bank_account_last_four}}</div>
@endif
{!! Former::checkbox('authorize_ach')
->text(trans('texts.ach_authorization', ['company'=>$account->getDisplayName(), 'email' => $account->work_email]))
->label(' ') !!}
{!! Former::checkbox('tos_agree')
->text(trans('texts.wepay_payment_tos_agree', [
'terms' => '<a href="https://go.wepay.com/terms-of-service-us" target="_blank">'.trans('texts.terms_of_service').'</a>',
'privacy_policy' => '<a href="https://go.wepay.com/privacy-policy-us" target="_blank">'.trans('texts.privacy_policy').'</a>',
]))
->help(trans('texts.payment_processed_through_wepay'))
->label(' ') !!}
<input type="hidden" name="sourceToken" value="{{$sourceId}}">
<p>&nbsp;</p>
<center>
@if(isset($amount) && empty($paymentMethodPending))
{!! Button::success(strtoupper(trans('texts.pay_now') . ' - ' . $account->formatMoney($amount, $client, true) ))
->submit()
->large() !!}
@else
{!! Button::success(strtoupper(trans('texts.add_bank_account') ))
->submit()
->large() !!}
@endif
</center>
@stop

View File

@ -91,8 +91,8 @@
</li>
@endif
@if (isset($account) && $account->getTokenGatewayId() && !$account->enable_client_portal_dashboard)
<li {{ Request::is('*client/paymentmethods') ? 'class="active"' : '' }}>
{!! link_to('/client/paymentmethods', trans('texts.payment_methods') ) !!}
<li {{ Request::is('*client/payment_methods') ? 'class="active"' : '' }}>
{!! link_to('/client/payment_methods', trans('texts.payment_methods') ) !!}
</li>
@endif
<li {{ Request::is('*client/payments') ? 'class="active"' : '' }}>