Merge branch 'develop' of github.com:hillelcoren/invoice-ninja into develop

Conflicts:
	app/Http/routes.php
	resources/lang/en/texts.php
This commit is contained in:
Hillel Coren 2016-05-16 11:34:00 +03:00
commit a82437b2bc
34 changed files with 1246 additions and 507 deletions

View File

@ -68,3 +68,11 @@ API_SECRET=password
#MAX_EMAIL_DOCUMENTS_SIZE # Total KB #MAX_EMAIL_DOCUMENTS_SIZE # Total KB
#MAX_ZIP_DOCUMENTS_SIZE # Total KB (uncompressed) #MAX_ZIP_DOCUMENTS_SIZE # Total KB (uncompressed)
#DOCUMENT_PREVIEW_SIZE # Pixels #DOCUMENT_PREVIEW_SIZE # Pixels
WEPAY_CLIENT_ID=
WEPAY_CLIENT_SECRET=
WEPAY_AUTO_UPDATE=true # Requires permission from WePay
WEPAY_ENVIRONMENT=production # production or stage
# See https://www.wepay.com/developer/reference/structures#theme
WEPAY_THEME={"name":"Invoice Ninja","primary_color":"0b4d78","secondary_color":"0b4d78","background_color":"f8f8f8","button_color":"33b753"}'));

View File

@ -1,5 +1,6 @@
<?php namespace App\Http\Controllers; <?php namespace App\Http\Controllers;
use App\Models\AccountGateway;
use Auth; use Auth;
use File; use File;
use Image; use Image;
@ -412,6 +413,7 @@ class AccountController extends BaseController
$account = Auth::user()->account; $account = Auth::user()->account;
$account->load('account_gateways'); $account->load('account_gateways');
$count = count($account->account_gateways); $count = count($account->account_gateways);
$trashedCount = AccountGateway::scope($account->id)->withTrashed()->count();
if ($accountGateway = $account->getGatewayConfig(GATEWAY_STRIPE)) { if ($accountGateway = $account->getGatewayConfig(GATEWAY_STRIPE)) {
if (! $accountGateway->getPublishableStripeKey()) { if (! $accountGateway->getPublishableStripeKey()) {
@ -419,10 +421,17 @@ class AccountController extends BaseController
} }
} }
if ($count == 0) { if ($trashedCount == 0) {
return Redirect::to('gateways/create'); return Redirect::to('gateways/create');
} else { } else {
$switchToWepay = WEPAY_CLIENT_ID && !$account->getGatewayConfig(GATEWAY_WEPAY);
if ($switchToWepay && $account->token_billing_type_id != TOKEN_BILLING_DISABLED) {
$switchToWepay = !$account->getGatewayConfig(GATEWAY_BRAINTREE) && !$account->getGatewayConfig(GATEWAY_STRIPE);
}
return View::make('accounts.payments', [ return View::make('accounts.payments', [
'showSwitchToWepay' => $switchToWepay,
'showAdd' => $count < count(Gateway::$paymentTypes), 'showAdd' => $count < count(Gateway::$paymentTypes),
'title' => trans('texts.online_payments') 'title' => trans('texts.online_payments')
]); ]);

View File

@ -11,6 +11,7 @@ use Validator;
use stdClass; use stdClass;
use URL; use URL;
use Utils; use Utils;
use WePay;
use App\Models\Gateway; use App\Models\Gateway;
use App\Models\Account; use App\Models\Account;
use App\Models\AccountGateway; use App\Models\AccountGateway;
@ -57,7 +58,6 @@ class AccountGatewayController extends BaseController
$data['paymentTypeId'] = $accountGateway->getPaymentType(); $data['paymentTypeId'] = $accountGateway->getPaymentType();
$data['selectGateways'] = Gateway::where('id', '=', $accountGateway->gateway_id)->get(); $data['selectGateways'] = Gateway::where('id', '=', $accountGateway->gateway_id)->get();
return View::make('accounts.account_gateway', $data); return View::make('accounts.account_gateway', $data);
} }
@ -101,35 +101,12 @@ class AccountGatewayController extends BaseController
private function getViewModel($accountGateway = false) private function getViewModel($accountGateway = false)
{ {
$selectedCards = $accountGateway ? $accountGateway->accepted_credit_cards : 0; $selectedCards = $accountGateway ? $accountGateway->accepted_credit_cards : 0;
$account = Auth::user()->account; $user = Auth::user();
$account =$user->account;
$paymentTypes = []; $paymentTypes = [];
foreach (Gateway::$paymentTypes as $type) { foreach (Gateway::$paymentTypes as $type) {
if ($accountGateway || !$account->getGatewayByType($type)) { if ($accountGateway || $account->canAddGateway($type)) {
if ($type == PAYMENT_TYPE_CREDIT_CARD && $account->getGatewayByType(PAYMENT_TYPE_STRIPE)) {
// Stripe is already handling credit card payments
continue;
}
if ($type == PAYMENT_TYPE_STRIPE && $account->getGatewayByType(PAYMENT_TYPE_CREDIT_CARD)) {
// Another gateway is already handling credit card payments
continue;
}
if ($type == PAYMENT_TYPE_DIRECT_DEBIT && $stripeGateway = $account->getGatewayByType(PAYMENT_TYPE_STRIPE)) {
if (!empty($stripeGateway->getAchEnabled())) {
// Stripe is already handling ACH payments
continue;
}
}
if ($type == PAYMENT_TYPE_PAYPAL && $braintreeGateway = $account->getGatewayConfig(GATEWAY_BRAINTREE)) {
if (!empty($braintreeGateway->getPayPalEnabled())) {
// PayPal is already enabled
continue;
}
}
$paymentTypes[$type] = $type == PAYMENT_TYPE_CREDIT_CARD ? trans('texts.other_providers'): trans('texts.'.strtolower($type)); $paymentTypes[$type] = $type == PAYMENT_TYPE_CREDIT_CARD ? trans('texts.other_providers'): trans('texts.'.strtolower($type));
if ($type == PAYMENT_TYPE_BITCOIN) { if ($type == PAYMENT_TYPE_BITCOIN) {
@ -158,7 +135,7 @@ class AccountGatewayController extends BaseController
foreach ($gateways as $gateway) { foreach ($gateways as $gateway) {
$fields = $gateway->getFields(); $fields = $gateway->getFields();
asort($fields); asort($fields);
$gateway->fields = $fields; $gateway->fields = $gateway->id == GATEWAY_WEPAY ? [] : $fields;
if ($accountGateway && $accountGateway->gateway_id == $gateway->id) { if ($accountGateway && $accountGateway->gateway_id == $gateway->id) {
$accountGateway->fields = $gateway->fields; $accountGateway->fields = $gateway->fields;
} }
@ -172,6 +149,7 @@ class AccountGatewayController extends BaseController
return [ return [
'paymentTypes' => $paymentTypes, 'paymentTypes' => $paymentTypes,
'account' => $account, 'account' => $account,
'user' => $user,
'accountGateway' => $accountGateway, 'accountGateway' => $accountGateway,
'config' => false, 'config' => false,
'gateways' => $gateways, 'gateways' => $gateways,
@ -188,7 +166,7 @@ class AccountGatewayController extends BaseController
$ids = Input::get('bulk_public_id'); $ids = Input::get('bulk_public_id');
$count = $this->accountGatewayService->bulk($ids, $action); $count = $this->accountGatewayService->bulk($ids, $action);
Session::flash('message', trans('texts.archived_account_gateway')); Session::flash('message', trans("texts.{$action}d_account_gateway"));
return Redirect::to('settings/' . ACCOUNT_PAYMENTS); return Redirect::to('settings/' . ACCOUNT_PAYMENTS);
} }
@ -236,14 +214,16 @@ class AccountGatewayController extends BaseController
} }
} }
if ($gatewayId != GATEWAY_WEPAY) {
foreach ($fields as $field => $details) { foreach ($fields as $field => $details) {
if (!in_array($field, $optional)) { if (!in_array($field, $optional)) {
if (strtolower($gateway->name) == 'beanstream') { if (strtolower($gateway->name) == 'beanstream') {
if (in_array($field, ['merchant_id', 'passCode'])) { if (in_array($field, ['merchant_id', 'passCode'])) {
$rules[$gateway->id.'_'.$field] = 'required'; $rules[$gateway->id . '_' . $field] = 'required';
} }
} else { } else {
$rules[$gateway->id.'_'.$field] = 'required'; $rules[$gateway->id . '_' . $field] = 'required';
}
} }
} }
} }
@ -265,11 +245,20 @@ class AccountGatewayController extends BaseController
} else { } else {
$accountGateway = AccountGateway::createNew(); $accountGateway = AccountGateway::createNew();
$accountGateway->gateway_id = $gatewayId; $accountGateway->gateway_id = $gatewayId;
if ($gatewayId == GATEWAY_WEPAY) {
if(!$this->setupWePay($accountGateway, $wepayResponse)) {
return $wepayResponse;
}
$oldConfig = $accountGateway->getConfig();
}
} }
$config = new stdClass(); $config = new stdClass();
if ($gatewayId != GATEWAY_WEPAY) {
foreach ($fields as $field => $details) { foreach ($fields as $field => $details) {
$value = trim(Input::get($gateway->id.'_'.$field)); $value = trim(Input::get($gateway->id . '_' . $field));
// if the new value is masked use the original value // if the new value is masked use the original value
if ($oldConfig && $value && $value === str_repeat('*', strlen($value))) { if ($oldConfig && $value && $value === str_repeat('*', strlen($value))) {
$value = $oldConfig->$field; $value = $oldConfig->$field;
@ -280,6 +269,9 @@ class AccountGatewayController extends BaseController
$config->$field = $value; $config->$field = $value;
} }
} }
} elseif($oldConfig) {
$config = clone $oldConfig;
}
$publishableKey = Input::get('publishable_key'); $publishableKey = Input::get('publishable_key');
if ($publishableKey = str_replace('*', '', $publishableKey)) { if ($publishableKey = str_replace('*', '', $publishableKey)) {
@ -340,6 +332,9 @@ class AccountGatewayController extends BaseController
$account->save(); $account->save();
} }
if(isset($wepayResponse)) {
return $wepayResponse;
} else {
if ($accountGatewayPublicId) { if ($accountGatewayPublicId) {
$message = trans('texts.updated_gateway'); $message = trans('texts.updated_gateway');
} else { } else {
@ -347,9 +342,153 @@ class AccountGatewayController extends BaseController
} }
Session::flash('message', $message); Session::flash('message', $message);
return Redirect::to("gateways/{$accountGateway->public_id}/edit"); return Redirect::to("gateways/{$accountGateway->public_id}/edit");
} }
} }
}
protected function getWePayUpdateUri($accountGateway)
{
if ($accountGateway->gateway_id != GATEWAY_WEPAY) {
return null;
}
$wepay = Utils::setupWePay($accountGateway);
$update_uri_data = $wepay->request('account/get_update_uri', array(
'account_id' => $accountGateway->getConfig()->accountId,
'mode' => 'iframe',
'redirect_uri' => URL::to('/gateways'),
));
return $update_uri_data->uri;
}
protected function setupWePay($accountGateway, &$response)
{
$user = Auth::user();
$account = $user->account;
$validator = Validator::make(Input::all(), array(
'company_name' => 'required',
'description' => 'required',
'tos_agree' => 'required',
'first_name' => 'required',
'last_name' => 'required',
'email' => 'required',
));
if ($validator->fails()) {
return Redirect::to('gateways/create')
->withErrors($validator)
->withInput();
}
try{
$wepay = Utils::setupWePay();
$wepayUser = $wepay->request('user/register/', array(
'client_id' => WEPAY_CLIENT_ID,
'client_secret' => WEPAY_CLIENT_SECRET,
'email' => Input::get('email'),
'first_name' => Input::get('first_name'),
'last_name' => Input::get('last_name'),
'original_ip' => \Request::getClientIp(true),
'original_device' => \Request::server('HTTP_USER_AGENT'),
'tos_acceptance_time' => time(),
'redirect_uri' => URL::to('gateways'),
'callback_uri' => URL::to(env('WEBHOOK_PREFIX','').'paymenthook/'.$account->account_key.'/'.GATEWAY_WEPAY),
'scope' => 'manage_accounts,collect_payments,view_user,preapprove_payments,send_money',
));
$accessToken = $wepayUser->access_token;
$accessTokenExpires = $wepayUser->expires_in ? (time() + $wepayUser->expires_in) : null;
$wepay = new WePay($accessToken);
$wepayAccount = $wepay->request('account/create/', array(
'name' => Input::get('company_name'),
'description' => Input::get('description'),
'theme_object' => json_decode(WEPAY_THEME),
));
try {
$wepay->request('user/send_confirmation/', []);
$confirmationRequired = true;
} catch(\WePayException $ex){
if ($ex->getMessage() == 'This access_token is already approved.') {
$confirmationRequired = false;
} else {
throw $ex;
}
}
if (($gateway = $account->getGatewayByType(PAYMENT_TYPE_CREDIT_CARD)) || ($gateway = $account->getGatewayByType(PAYMENT_TYPE_STRIPE))) {
$gateway->delete();
}
$accountGateway->gateway_id = GATEWAY_WEPAY;
$accountGateway->setConfig(array(
'userId' => $wepayUser->user_id,
'accessToken' => $accessToken,
'tokenType' => $wepayUser->token_type,
'tokenExpires' => $accessTokenExpires,
'accountId' => $wepayAccount->account_id,
'testMode' => WEPAY_ENVIRONMENT == WEPAY_STAGE,
));
if ($confirmationRequired) {
Session::flash('message', trans('texts.created_wepay_confirmation_required'));
} else {
$updateUri = $wepay->request('/account/get_update_uri', array(
'account_id' => $wepayAccount->account_id,
'redirect_uri' => URL::to('gateways'),
));
$response = Redirect::to($updateUri->uri);
return true;
}
$response = Redirect::to("gateways/{$accountGateway->public_id}/edit");
return true;
} catch (\WePayException $e) {
Session::flash('error', $e->getMessage());
$response = Redirect::to('gateways/create')
->withInput();
return false;
}
}
public function resendConfirmation($publicId = false)
{
$accountGateway = AccountGateway::scope($publicId)->firstOrFail();
if ($accountGateway->gateway_id == GATEWAY_WEPAY) {
try {
$wepay = Utils::setupWePay($accountGateway);
$wepay->request('user/send_confirmation', []);
Session::flash('message', trans('texts.resent_confirmation_email'));
} catch (\WePayException $e) {
Session::flash('error', $e->getMessage());
}
}
return Redirect::to("gateways/{$accountGateway->public_id}/edit");
}
public function switchToWepay()
{
$data = self::getViewModel();
$data['url'] = 'gateways';
$data['method'] = 'POST';
unset($data['gateways']);
if ( ! \Request::secure() && ! Utils::isNinjaDev()) {
Session::flash('warning', trans('texts.enable_https'));
}
return View::make('accounts.account_gateway_switch_wepay', $data);
}
} }

View File

@ -1,7 +1,7 @@
<?php namespace App\Http\Controllers; <?php namespace App\Http\Controllers;
use App\Models\Expense; use App\Models\Expense;
use app\Ninja\Repositories\ExpenseRepository; use App\Ninja\Repositories\ExpenseRepository;
use App\Ninja\Transformers\ExpenseTransformer; use App\Ninja\Transformers\ExpenseTransformer;
use App\Services\ExpenseService; use App\Services\ExpenseService;
use Utils; use Utils;

View File

@ -152,6 +152,8 @@ class PaymentController extends BaseController
$data = array(); $data = array();
Session::put($invitation->id.'payment_ref', $invoice->id.'_'.uniqid());
if ($paymentType != PAYMENT_TYPE_BRAINTREE_PAYPAL) { if ($paymentType != PAYMENT_TYPE_BRAINTREE_PAYPAL) {
if ($paymentType == PAYMENT_TYPE_TOKEN) { if ($paymentType == PAYMENT_TYPE_TOKEN) {
$useToken = true; $useToken = true;
@ -198,6 +200,10 @@ class PaymentController extends BaseController
$data['braintreeClientToken'] = $this->paymentService->getBraintreeClientToken($account); $data['braintreeClientToken'] = $this->paymentService->getBraintreeClientToken($account);
} }
if(!empty($data['braintreeClientToken']) || $accountGateway->getPublishableStripeKey()|| $accountGateway->gateway_id == GATEWAY_WEPAY) {
$data['tokenize'] = true;
}
} else { } else {
if ($deviceData = Input::get('details')) { if ($deviceData = Input::get('details')) {
Session::put($invitation->id . 'device_data', $deviceData); Session::put($invitation->id . 'device_data', $deviceData);
@ -405,7 +411,7 @@ class PaymentController extends BaseController
'last_name' => 'required', 'last_name' => 'required',
]; ];
if ( ! Input::get('stripeToken') && ! Input::get('payment_method_nonce') && !(Input::get('plaidPublicToken') && Input::get('plaidAccountId'))) { if ( !Input::get('sourceToken') && !(Input::get('plaidPublicToken') && Input::get('plaidAccountId'))) {
$rules = array_merge( $rules = array_merge(
$rules, $rules,
[ [
@ -433,7 +439,7 @@ class PaymentController extends BaseController
$validator = Validator::make(Input::all(), $rules); $validator = Validator::make(Input::all(), $rules);
if ($validator->fails()) { if ($validator->fails()) {
return false; return $validator;
} }
if ($requireAddress && $accountGateway->update_address) { if ($requireAddress && $accountGateway->update_address) {
@ -460,8 +466,6 @@ class PaymentController extends BaseController
$accountGateway = $account->getGatewayByType($paymentType); $accountGateway = $account->getGatewayByType($paymentType);
$paymentMethod = null; $paymentMethod = null;
if ($useToken) { if ($useToken) {
if(!$sourceId) { if(!$sourceId) {
Session::flash('error', trans('texts.no_payment_method_specified')); Session::flash('error', trans('texts.no_payment_method_specified'));
@ -473,12 +477,13 @@ class PaymentController extends BaseController
} }
} }
if (!static::processPaymentClientDetails($client, $accountGateway, $paymentType, $onSite)) { if (($validator = static::processPaymentClientDetails($client, $accountGateway, $paymentType, $onSite)) !== true) {
return Redirect::to('payment/'.$invitationKey) return Redirect::to('payment/'.$invitationKey)
->withErrors($validator) ->withErrors($validator)
->withInput(Request::except('cvv')); ->withInput(Request::except('cvv'));
} }
try { try {
// For offsite payments send the client's details on file // 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 we're using a token then we don't need to send any other data
@ -492,21 +497,48 @@ class PaymentController extends BaseController
$details = $this->paymentService->getPaymentDetails($invitation, $accountGateway, $data); $details = $this->paymentService->getPaymentDetails($invitation, $accountGateway, $data);
// check if we're creating/using a billing token // check if we're creating/using a billing token
$tokenBillingSupported = false;
$sourceReferenceParam = 'token';
if ($accountGateway->gateway_id == GATEWAY_STRIPE) { if ($accountGateway->gateway_id == GATEWAY_STRIPE) {
$tokenBillingSupported = true;
$customerReferenceParam = 'customerReference';
if ($paymentType == PAYMENT_TYPE_STRIPE_ACH && !Input::get('authorize_ach')) { if ($paymentType == PAYMENT_TYPE_STRIPE_ACH && !Input::get('authorize_ach')) {
Session::flash('error', trans('texts.ach_authorization_required')); Session::flash('error', trans('texts.ach_authorization_required'));
return Redirect::to('payment/'.$invitationKey)->withInput(Request::except('cvv')); return Redirect::to('payment/'.$invitationKey)->withInput(Request::except('cvv'));
} }
} 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 ($useToken) {
$details['customerReference'] = $customerReference; if ($customerReferenceParam) {
unset($details['token']); $details[$customerReferenceParam] = $customerReference;
$details['cardReference'] = $sourceReference; }
$details[$sourceReferenceParam] = $sourceReference;
unset($details['card']);
} elseif ($account->token_billing_type_id == TOKEN_BILLING_ALWAYS || Input::get('token_billing') || $paymentType == PAYMENT_TYPE_STRIPE_ACH) { } elseif ($account->token_billing_type_id == TOKEN_BILLING_ALWAYS || Input::get('token_billing') || $paymentType == PAYMENT_TYPE_STRIPE_ACH) {
$token = $this->paymentService->createToken($gateway, $details, $accountGateway, $client, $invitation->contact_id, $customerReference/* return parameter */, $paymentMethod/* return parameter */); $token = $this->paymentService->createToken($gateway, $details, $accountGateway, $client, $invitation->contact_id, $customerReference/* return parameter */, $paymentMethod/* return parameter */);
if ($token) { if ($token) {
$details['token'] = $token; $details[$sourceReferenceParam] = $token;
$details['customerReference'] = $customerReference; if ($customerReferenceParam) {
$details[$customerReferenceParam] = $customerReference;
}
if ($paymentType == PAYMENT_TYPE_STRIPE_ACH && empty(Input::get('plaidPublicToken')) ) { if ($paymentType == PAYMENT_TYPE_STRIPE_ACH && empty(Input::get('plaidPublicToken')) ) {
// The user needs to complete verification // The user needs to complete verification
@ -518,36 +550,6 @@ class PaymentController extends BaseController
return Redirect::to('payment/'.$invitationKey)->withInput(Request::except('cvv')); return Redirect::to('payment/'.$invitationKey)->withInput(Request::except('cvv'));
} }
} }
} elseif ($accountGateway->gateway_id == GATEWAY_BRAINTREE) {
$deviceData = Input::get('device_data');
if (!$deviceData) {
$deviceData = Session::get($invitation->id . 'device_data');
}
if ($token = Input::get('payment_method_nonce')) {
$details['token'] = $token;
unset($details['card']);
}
if ($useToken) {
$details['customerId'] = $customerReference;
$details['paymentMethodToken'] = $sourceReference;
unset($details['token']);
} elseif ($account->token_billing_type_id == TOKEN_BILLING_ALWAYS || Input::get('token_billing')) {
$token = $this->paymentService->createToken($gateway, $details, $accountGateway, $client, $invitation->contact_id, $customerReference/* return parameter */, $paymentMethod/* return parameter */);
if ($token) {
$details['paymentMethodToken'] = $token;
$details['customerId'] = $customerReference;
unset($details['token']);
} else {
$this->error('Token-No-Ref', $this->paymentService->lastError, $accountGateway);
return Redirect::to('payment/'.$invitationKey)->withInput(Request::except('cvv'));
}
}
if($deviceData) {
$details['deviceData'] = $deviceData;
}
} }
$response = $gateway->purchase($details)->send(); $response = $gateway->purchase($details)->send();
@ -569,7 +571,7 @@ class PaymentController extends BaseController
if (!$ref) { if (!$ref) {
$this->error('No-Ref', $response->getMessage(), $accountGateway); $this->error('No-Ref', $response->getMessage(), $accountGateway);
if ($onSite) { if ($onSite && $paymentType != PAYMENT_TYPE_BRAINTREE_PAYPAL) {
return Redirect::to('payment/'.$invitationKey) return Redirect::to('payment/'.$invitationKey)
->withInput(Request::except('cvv')); ->withInput(Request::except('cvv'));
} else { } else {
@ -597,7 +599,7 @@ class PaymentController extends BaseController
$response->redirect(); $response->redirect();
} else { } else {
$this->error('Unknown', $response->getMessage(), $accountGateway); $this->error('Unknown', $response->getMessage(), $accountGateway);
if ($onSite) { if ($onSite && $paymentType != PAYMENT_TYPE_BRAINTREE_PAYPAL) {
return Redirect::to('payment/'.$invitationKey)->withInput(Request::except('cvv')); return Redirect::to('payment/'.$invitationKey)->withInput(Request::except('cvv'));
} else { } else {
return Redirect::to('view/'.$invitationKey); return Redirect::to('view/'.$invitationKey);
@ -605,7 +607,7 @@ class PaymentController extends BaseController
} }
} catch (\Exception $e) { } catch (\Exception $e) {
$this->error('Uncaught', false, $accountGateway, $e); $this->error('Uncaught', false, $accountGateway, $e);
if ($onSite) { if ($onSite && $paymentType != PAYMENT_TYPE_BRAINTREE_PAYPAL) {
return Redirect::to('payment/'.$invitationKey)->withInput(Request::except('cvv')); return Redirect::to('payment/'.$invitationKey)->withInput(Request::except('cvv'));
} else { } else {
return Redirect::to('view/'.$invitationKey); return Redirect::to('view/'.$invitationKey);
@ -760,7 +762,7 @@ class PaymentController extends BaseController
'message' => $data, 'message' => $data,
], 500); ], 500);
} elseif (!empty($data)) { } elseif (!empty($data)) {
return $data; return response()->json($data);
} }
return response()->json([ return response()->json([
@ -791,6 +793,8 @@ class PaymentController extends BaseController
switch($gatewayId) { switch($gatewayId) {
case GATEWAY_STRIPE: case GATEWAY_STRIPE:
return $this->handleStripeWebhook($accountGateway); return $this->handleStripeWebhook($accountGateway);
case GATEWAY_WEPAY:
return $this->handleWePayWebhook($accountGateway);
default: default:
return response()->json([ return response()->json([
'message' => 'Unsupported gateway', 'message' => 'Unsupported gateway',
@ -798,6 +802,50 @@ class PaymentController extends BaseController
} }
} }
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');
} else {
return array('message' => 'Ignoring event');
}
}
protected function handleStripeWebhook($accountGateway) { protected function handleStripeWebhook($accountGateway) {
$eventId = Input::get('id'); $eventId = Input::get('id');
$eventType= Input::get('type'); $eventType= Input::get('type');

View File

@ -16,7 +16,7 @@ use Redirect;
use App\Models\Gateway; use App\Models\Gateway;
use App\Models\Invitation; use App\Models\Invitation;
use App\Models\Document; use App\Models\Document;
use App\ModelsPaymentMethod; use App\Models\PaymentMethod;
use App\Ninja\Repositories\InvoiceRepository; use App\Ninja\Repositories\InvoiceRepository;
use App\Ninja\Repositories\PaymentRepository; use App\Ninja\Repositories\PaymentRepository;
use App\Ninja\Repositories\ActivityRepository; use App\Ninja\Repositories\ActivityRepository;
@ -176,8 +176,10 @@ class PublicClientController extends BaseController
$html = ''; $html = '';
if ($paymentMethod->payment_type_id == PAYMENT_TYPE_ACH) { if ($paymentMethod->payment_type_id == PAYMENT_TYPE_ACH) {
if($paymentMethod->bank_data) { if ($paymentMethod->bank_data) {
$html = '<div>' . htmlentities($paymentMethod->bank_data->name) . '</div>'; $html = '<div>' . htmlentities($paymentMethod->bank_data->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) { } 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).'">'; $html = '<img height="22" src="'.URL::to('/images/credit_cards/paypal.png').'" alt="'.trans("texts.card_".$code).'">';
@ -860,7 +862,6 @@ class PublicClientController extends BaseController
]; ];
if ($paymentType == PAYMENT_TYPE_STRIPE_ACH) { if ($paymentType == PAYMENT_TYPE_STRIPE_ACH) {
$data['currencies'] = Cache::get('currencies'); $data['currencies'] = Cache::get('currencies');
} }
@ -868,6 +869,10 @@ class PublicClientController extends BaseController
$data['braintreeClientToken'] = $this->paymentService->getBraintreeClientToken($account); $data['braintreeClientToken'] = $this->paymentService->getBraintreeClientToken($account);
} }
if(!empty($data['braintreeClientToken']) || $accountGateway->getPublishableStripeKey()|| $accountGateway->gateway_id == GATEWAY_WEPAY) {
$data['tokenize'] = true;
}
return View::make('payments.add_paymentmethod', $data); return View::make('payments.add_paymentmethod', $data);
} }
@ -883,10 +888,12 @@ class PublicClientController extends BaseController
$account = $client->account; $account = $client->account;
$accountGateway = $account->getGatewayByType($paymentType); $accountGateway = $account->getGatewayByType($paymentType);
$sourceToken = $accountGateway->gateway_id == GATEWAY_STRIPE ? Input::get('stripeToken'):Input::get('payment_method_nonce'); $sourceToken = Input::get('sourceToken');
if (!PaymentController::processPaymentClientDetails($client, $accountGateway, $paymentType)) { if (($validator = PaymentController::processPaymentClientDetails($client, $accountGateway, $paymentType)) !== true) {
return Redirect::to('client/paymentmethods/add/' . $typeLink)->withInput(Request::except('cvv')); return Redirect::to('client/paymentmethods/add/' . $typeLink)
->withErrors($validator)
->withInput(Request::except('cvv'));
} }
if ($sourceToken) { if ($sourceToken) {

View File

@ -242,6 +242,8 @@ Route::group([
Route::post('/import_csv', 'ImportController@doImportCSV'); Route::post('/import_csv', 'ImportController@doImportCSV');
Route::resource('gateways', 'AccountGatewayController'); Route::resource('gateways', 'AccountGatewayController');
Route::get('gateways/{public_id}/resend_confirmation', 'AccountGatewayController@resendConfirmation');
Route::get('gateways/switch/wepay', 'AccountGatewayController@switchToWepay');
Route::get('api/gateways', array('as'=>'api.gateways', 'uses'=>'AccountGatewayController@getDatatable')); Route::get('api/gateways', array('as'=>'api.gateways', 'uses'=>'AccountGatewayController@getDatatable'));
Route::post('account_gateways/bulk', 'AccountGatewayController@bulk'); Route::post('account_gateways/bulk', 'AccountGatewayController@bulk');
@ -563,6 +565,10 @@ if (!defined('CONTACT_EMAIL')) {
define('GATEWAY_WEPAY', 60); define('GATEWAY_WEPAY', 60);
define('GATEWAY_BRAINTREE', 61); define('GATEWAY_BRAINTREE', 61);
// The customer exists, but only as a local concept
// The remote gateway doesn't understand the concept of customers
define('CUSTOMER_REFERENCE_LOCAL', 'local');
define('EVENT_CREATE_CLIENT', 1); define('EVENT_CREATE_CLIENT', 1);
define('EVENT_CREATE_INVOICE', 2); define('EVENT_CREATE_INVOICE', 2);
define('EVENT_CREATE_QUOTE', 3); define('EVENT_CREATE_QUOTE', 3);
@ -704,10 +710,10 @@ if (!defined('CONTACT_EMAIL')) {
define('RESELLER_REVENUE_SHARE', 'A'); define('RESELLER_REVENUE_SHARE', 'A');
define('RESELLER_LIMITED_USERS', 'B'); define('RESELLER_LIMITED_USERS', 'B');
define('AUTO_BILL_OFF', 0); define('AUTO_BILL_OFF', 1);
define('AUTO_BILL_OPT_IN', 1); define('AUTO_BILL_OPT_IN', 2);
define('AUTO_BILL_OPT_OUT', 2); define('AUTO_BILL_OPT_OUT', 3);
define('AUTO_BILL_ALWAYS', 3); define('AUTO_BILL_ALWAYS', 4);
// These must be lowercase // These must be lowercase
define('PLAN_FREE', 'free'); define('PLAN_FREE', 'free');
@ -748,6 +754,15 @@ if (!defined('CONTACT_EMAIL')) {
// Pro users who started paying on or before this date will be able to manage users // Pro users who started paying on or before this date will be able to manage users
define('PRO_USERS_GRANDFATHER_DEADLINE', '2016-05-15'); define('PRO_USERS_GRANDFATHER_DEADLINE', '2016-05-15');
// WePay
define('WEPAY_PRODUCTION', 'production');
define('WEPAY_STAGE', 'stage');
define('WEPAY_CLIENT_ID', env('WEPAY_CLIENT_ID'));
define('WEPAY_CLIENT_SECRET', env('WEPAY_CLIENT_SECRET'));
define('WEPAY_AUTO_UPDATE', env('WEPAY_AUTO_UPDATE', false));
define('WEPAY_ENVIRONMENT', env('WEPAY_ENVIRONMENT', WEPAY_PRODUCTION));
define('WEPAY_THEME', env('WEPAY_THEME','{"name":"Invoice Ninja","primary_color":"0b4d78","secondary_color":"0b4d78","background_color":"f8f8f8","button_color":"33b753"}'));
$creditCards = [ $creditCards = [
1 => ['card' => 'images/credit_cards/Test-Visa-Icon.png', 'text' => 'Visa'], 1 => ['card' => 'images/credit_cards/Test-Visa-Icon.png', 'text' => 'Visa'],
2 => ['card' => 'images/credit_cards/Test-MasterCard-Icon.png', 'text' => 'Master Card'], 2 => ['card' => 'images/credit_cards/Test-MasterCard-Icon.png', 'text' => 'Master Card'],

View File

@ -15,6 +15,7 @@ use Log;
use DateTime; use DateTime;
use stdClass; use stdClass;
use Carbon; use Carbon;
use WePay;
use App\Models\Currency; use App\Models\Currency;
@ -995,4 +996,21 @@ class Utils
return $url; return $url;
} }
public static function setupWePay($accountGateway = null)
{
if (WePay::getEnvironment() == 'none') {
if (WEPAY_ENVIRONMENT == WEPAY_STAGE) {
WePay::useStaging(WEPAY_CLIENT_ID, WEPAY_CLIENT_SECRET);
} else {
WePay::useProduction(WEPAY_CLIENT_ID, WEPAY_CLIENT_SECRET);
}
}
if ($accountGateway) {
return new WePay($accountGateway->getConfig()->accessToken);
} else {
return new WePay(null);
}
}
} }

View File

@ -307,8 +307,8 @@ class ActivityListener
$this->activityRepo->create( $this->activityRepo->create(
$payment, $payment,
ACTIVITY_TYPE_DELETE_PAYMENT, ACTIVITY_TYPE_DELETE_PAYMENT,
$payment->amount, $payment->amount - $payment->refunded,
$payment->amount * -1 ($payment->amount - $payment->refunded) * -1
); );
} }
@ -343,8 +343,8 @@ class ActivityListener
$this->activityRepo->create( $this->activityRepo->create(
$payment, $payment,
ACTIVITY_TYPE_FAILED_PAYMENT, ACTIVITY_TYPE_FAILED_PAYMENT,
$payment->amount, ($payment->amount - $payment->refunded),
$payment->amount * -1 ($payment->amount - $payment->refunded) * -1
); );
} }
@ -367,8 +367,8 @@ class ActivityListener
$this->activityRepo->create( $this->activityRepo->create(
$payment, $payment,
ACTIVITY_TYPE_RESTORE_PAYMENT, ACTIVITY_TYPE_RESTORE_PAYMENT,
$event->fromDeleted ? $payment->amount * -1 : 0, $event->fromDeleted ? ($payment->amount - $payment->refunded) * -1 : 0,
$event->fromDeleted ? $payment->amount : 0 $event->fromDeleted ? ($payment->amount - $payment->refunded) : 0
); );
} }
} }

View File

@ -1245,6 +1245,8 @@ class Account extends Eloquent
return GATEWAY_STRIPE; return GATEWAY_STRIPE;
} elseif ($this->isGatewayConfigured(GATEWAY_BRAINTREE)) { } elseif ($this->isGatewayConfigured(GATEWAY_BRAINTREE)) {
return GATEWAY_BRAINTREE; return GATEWAY_BRAINTREE;
} elseif ($this->isGatewayConfigured(GATEWAY_WEPAY)) {
return GATEWAY_WEPAY;
} else { } else {
return false; return false;
} }
@ -1410,6 +1412,37 @@ class Account extends Eloquent
public function getFontFolders(){ public function getFontFolders(){
return array_map(function($item){return $item['folder'];}, $this->getFontsData()); return array_map(function($item){return $item['folder'];}, $this->getFontsData());
} }
public function canAddGateway($type){
if($this->getGatewayByType($type)) {
return false;
}
if ($type == PAYMENT_TYPE_CREDIT_CARD && $this->getGatewayByType(PAYMENT_TYPE_STRIPE)) {
// Stripe is already handling credit card payments
return false;
}
if ($type == PAYMENT_TYPE_STRIPE && $this->getGatewayByType(PAYMENT_TYPE_CREDIT_CARD)) {
// Another gateway is already handling credit card payments
return false;
}
if ($type == PAYMENT_TYPE_DIRECT_DEBIT && $stripeGateway = $this->getGatewayByType(PAYMENT_TYPE_STRIPE)) {
if (!empty($stripeGateway->getAchEnabled())) {
// Stripe is already handling ACH payments
return false;
}
}
if ($type == PAYMENT_TYPE_PAYPAL && $braintreeGateway = $this->getGatewayConfig(GATEWAY_BRAINTREE)) {
if (!empty($braintreeGateway->getPayPalEnabled())) {
// PayPal is already enabled
return false;
}
}
return true;
}
} }
Account::updated(function ($account) { Account::updated(function ($account) {

View File

@ -77,7 +77,7 @@ class AccountGateway extends EntityModel
return !empty($this->getConfigField('enableAch')); return !empty($this->getConfigField('enableAch'));
} }
public function getPayPAlEnabled() public function getPayPalEnabled()
{ {
return !empty($this->getConfigField('enablePayPal')); return !empty($this->getConfigField('enablePayPal'));
} }

View File

@ -167,7 +167,7 @@ class Payment extends EntityModel
return ENTITY_PAYMENT; return ENTITY_PAYMENT;
} }
public function getBankData() public function getBankDataAttribute()
{ {
if (!$this->routing_number) { if (!$this->routing_number) {
return null; return null;

View File

@ -63,7 +63,7 @@ class PaymentMethod extends EntityModel
return $this->hasMany('App\Models\Payments'); return $this->hasMany('App\Models\Payments');
} }
public function getBankData() public function getBankDataAttribute()
{ {
if (!$this->routing_number) { if (!$this->routing_number) {
return null; return null;

View File

@ -15,10 +15,14 @@ class AccountGatewayRepository extends BaseRepository
public function find($accountId) public function find($accountId)
{ {
return DB::table('account_gateways') $query = DB::table('account_gateways')
->join('gateways', 'gateways.id', '=', 'account_gateways.gateway_id') ->join('gateways', 'gateways.id', '=', 'account_gateways.gateway_id')
->where('account_gateways.deleted_at', '=', null) ->where('account_gateways.account_id', '=', $accountId);
->where('account_gateways.account_id', '=', $accountId)
->select('account_gateways.public_id', 'gateways.name', 'account_gateways.deleted_at', 'account_gateways.gateway_id'); if (!\Session::get('show_trash:gateway')) {
$query->where('account_gateways.deleted_at', '=', null);
}
return $query->select('account_gateways.id', 'account_gateways.public_id', 'gateways.name', 'account_gateways.deleted_at', 'account_gateways.gateway_id');
} }
} }

View File

@ -324,7 +324,7 @@ class InvoiceRepository extends BaseRepository
$invoice->start_date = Utils::toSqlDate($data['start_date']); $invoice->start_date = Utils::toSqlDate($data['start_date']);
$invoice->end_date = Utils::toSqlDate($data['end_date']); $invoice->end_date = Utils::toSqlDate($data['end_date']);
$invoice->client_enable_auto_bill = isset($data['client_enable_auto_bill']) && $data['client_enable_auto_bill'] ? true : false; $invoice->client_enable_auto_bill = isset($data['client_enable_auto_bill']) && $data['client_enable_auto_bill'] ? true : false;
$invoice->auto_bill = isset($data['auto_bill']) ? intval($data['auto_bill']) : 0; $invoice->auto_bill = isset($data['auto_bill']) ? intval($data['auto_bill']) : AUTO_BILL_OFF;
if ($invoice->auto_bill < AUTO_BILL_OFF || $invoice->auto_bill > AUTO_BILL_ALWAYS ) { if ($invoice->auto_bill < AUTO_BILL_OFF || $invoice->auto_bill > AUTO_BILL_ALWAYS ) {
$invoice->auto_bill = AUTO_BILL_OFF; $invoice->auto_bill = AUTO_BILL_OFF;

View File

@ -2,6 +2,7 @@
use URL; use URL;
use App\Models\Gateway; use App\Models\Gateway;
use App\Models\AccountGateway;
use App\Services\BaseService; use App\Services\BaseService;
use App\Ninja\Repositories\AccountGatewayRepository; use App\Ninja\Repositories\AccountGatewayRepository;
@ -41,7 +42,40 @@ class AccountGatewayService extends BaseService
[ [
'name', 'name',
function ($model) { function ($model) {
if ($model->deleted_at) {
return $model->name;
} elseif ($model->gateway_id != GATEWAY_WEPAY) {
return link_to("gateways/{$model->public_id}/edit", $model->name)->toHtml(); return link_to("gateways/{$model->public_id}/edit", $model->name)->toHtml();
} else {
$accountGateway = AccountGateway::find($model->id);
$endpoint = WEPAY_ENVIRONMENT == WEPAY_STAGE ? 'https://stage.wepay.com/' : 'https://www.wepay.com/';
$wepayAccountId = $accountGateway->getConfig()->accountId;
$linkText = $model->name;
$url = $endpoint.'account/'.$wepayAccountId;
$wepay = \Utils::setupWepay($accountGateway);
$html = link_to($url, $linkText, array('target'=>'_blank'))->toHtml();
try {
$wepayAccount = $wepay->request('/account', array('account_id' => $wepayAccountId));
if ($wepayAccount->state == 'action_required') {
$updateUri = $wepay->request('/account/get_update_uri', array(
'account_id' => $wepayAccountId,
'redirect_uri' => URL::to('gateways'),
));
$linkText .= ' <span style="color:#d9534f">('.trans('texts.action_required').')</span>';
$url = $updateUri->uri;
$html = "<a href=\"{$url}\">{$linkText}</a>";
$model->setupUrl = $url;
} elseif ($wepayAccount->state == 'pending') {
$linkText .= ' ('.trans('texts.resend_confirmation_email').')';
$model->resendConfirmationUrl = $url = URL::to("gateways/{$accountGateway->public_id}/resend_confirmation");
$html = link_to($url, $linkText)->toHtml();
}
} catch(\WePayException $ex){}
return $html;
}
} }
], ],
[ [
@ -57,9 +91,41 @@ class AccountGatewayService extends BaseService
{ {
return [ return [
[ [
uctrans('texts.resend_confirmation_email'),
function ($model) {
return $model->resendConfirmationUrl;
},
function($model) {
return !$model->deleted_at && $model->gateway_id == GATEWAY_WEPAY && !empty($model->resendConfirmationUrl);
}
], [
uctrans('texts.finish_setup'),
function ($model) {
return $model->setupUrl;
},
function($model) {
return !$model->deleted_at && $model->gateway_id == GATEWAY_WEPAY && !empty($model->setupUrl);
}
] , [
uctrans('texts.edit_gateway'), uctrans('texts.edit_gateway'),
function ($model) { function ($model) {
return URL::to("gateways/{$model->public_id}/edit"); return URL::to("gateways/{$model->public_id}/edit");
},
function($model) {
return !$model->deleted_at;
}
], [
uctrans('texts.manage_wepay_account'),
function ($model) {
$accountGateway = AccountGateway::find($model->id);
$endpoint = WEPAY_ENVIRONMENT == WEPAY_STAGE ? 'https://stage.wepay.com/' : 'https://www.wepay.com/';
return array(
'url' => $endpoint.'account/'.$accountGateway->getConfig()->accountId,
'attributes' => 'target="_blank"'
);
},
function($model) {
return !$model->deleted_at && $model->gateway_id == GATEWAY_WEPAY;
} }
] ]
]; ];

View File

@ -60,11 +60,7 @@ class DatatableService
$str .= '<div class="tr-status"></div>'; $str .= '<div class="tr-status"></div>';
} }
$str .= '<div class="btn-group tr-action" style="height:auto;display:none"> $dropdown_contents = '';
<button type="button" class="btn btn-xs btn-default dropdown-toggle" data-toggle="dropdown" style="width:100px">
'.trans('texts.select').' <span class="caret"></span>
</button>
<ul class="dropdown-menu" role="menu">';
$lastIsDivider = false; $lastIsDivider = false;
if (!$model->deleted_at || $model->deleted_at == '0000-00-00') { if (!$model->deleted_at || $model->deleted_at == '0000-00-00') {
@ -78,17 +74,24 @@ class DatatableService
list($value, $url, $visible) = $action; list($value, $url, $visible) = $action;
if ($visible($model)) { if ($visible($model)) {
if($value == '--divider--'){ if($value == '--divider--'){
$str .= "<li class=\"divider\"></li>"; $dropdown_contents .= "<li class=\"divider\"></li>";
$lastIsDivider = true; $lastIsDivider = true;
} }
else { else {
$str .= "<li><a href=\"{$url($model)}\">{$value}</a></li>"; $urlVal = $url($model);
$urlStr = is_string($urlVal) ? $urlVal : $urlVal['url'];
$attributes = '';
if (!empty($urlVal['attributes'])) {
$attributes = ' '.$urlVal['attributes'];
}
$dropdown_contents .= "<li><a href=\"$urlStr\"{$attributes}>{$value}</a></li>";
$hasAction = true; $hasAction = true;
$lastIsDivider = false; $lastIsDivider = false;
} }
} }
} elseif ( ! $lastIsDivider) { } elseif ( ! $lastIsDivider) {
$str .= "<li class=\"divider\"></li>"; $dropdown_contents .= "<li class=\"divider\"></li>";
$lastIsDivider = true; $lastIsDivider = true;
} }
} }
@ -98,24 +101,35 @@ class DatatableService
} }
if ( $can_edit && ! $lastIsDivider) { if ( $can_edit && ! $lastIsDivider) {
$str .= "<li class=\"divider\"></li>"; $dropdown_contents .= "<li class=\"divider\"></li>";
} }
if (($entityType != ENTITY_USER || $model->public_id) && $can_edit) { if (($entityType != ENTITY_USER || $model->public_id) && $can_edit) {
$str .= "<li><a href=\"javascript:archiveEntity({$model->public_id})\">" $dropdown_contents .= "<li><a href=\"javascript:archiveEntity({$model->public_id})\">"
. trans("texts.archive_{$entityType}") . "</a></li>"; . trans("texts.archive_{$entityType}") . "</a></li>";
} }
} else if($can_edit) { } else if($can_edit) {
$str .= "<li><a href=\"javascript:restoreEntity({$model->public_id})\">" if ($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_{$entityType}") . "</a></li>"; . trans("texts.restore_{$entityType}") . "</a></li>";
} }
}
if (property_exists($model, 'is_deleted') && !$model->is_deleted && $can_edit) { if (property_exists($model, 'is_deleted') && !$model->is_deleted && $can_edit) {
$str .= "<li><a href=\"javascript:deleteEntity({$model->public_id})\">" $dropdown_contents .= "<li><a href=\"javascript:deleteEntity({$model->public_id})\">"
. trans("texts.delete_{$entityType}") . "</a></li>"; . trans("texts.delete_{$entityType}") . "</a></li>";
} }
return $str.'</ul></div></center>'; if (!empty($dropdown_contents)) {
$str .= '<div class="btn-group tr-action" style="height:auto;display:none">
<button type="button" class="btn btn-xs btn-default dropdown-toggle" data-toggle="dropdown" style="width:100px">
'.trans('texts.select').' <span class="caret"></span>
</button>
<ul class="dropdown-menu" role="menu">';
$str .= $dropdown_contents . '</ul>';
}
return $str.'</div></center>';
}); });
} }

View File

@ -9,6 +9,7 @@ use Cache;
use Omnipay; use Omnipay;
use Session; use Session;
use CreditCard; use CreditCard;
use WePay;
use App\Models\Payment; use App\Models\Payment;
use App\Models\PaymentMethod; use App\Models\PaymentMethod;
use App\Models\Account; use App\Models\Account;
@ -30,7 +31,8 @@ class PaymentService extends BaseService
protected static $refundableGateways = array( protected static $refundableGateways = array(
GATEWAY_STRIPE, GATEWAY_STRIPE,
GATEWAY_BRAINTREE GATEWAY_BRAINTREE,
GATEWAY_WEPAY,
); );
public function __construct(PaymentRepository $paymentRepo, AccountRepository $accountRepo, DatatableService $datatableService) public function __construct(PaymentRepository $paymentRepo, AccountRepository $accountRepo, DatatableService $datatableService)
@ -95,9 +97,9 @@ class PaymentService extends BaseService
$data['ButtonSource'] = 'InvoiceNinja_SP'; $data['ButtonSource'] = 'InvoiceNinja_SP';
}; };
if ($input && $accountGateway->isGateway(GATEWAY_STRIPE)) { if ($input) {
if (!empty($input['stripeToken'])) { if (!empty($input['sourceToken'])) {
$data['token'] = $input['stripeToken']; $data['token'] = $input['sourceToken'];
unset($data['card']); unset($data['card']);
} elseif (!empty($input['plaidPublicToken'])) { } elseif (!empty($input['plaidPublicToken'])) {
$data['plaidPublicToken'] = $input['plaidPublicToken']; $data['plaidPublicToken'] = $input['plaidPublicToken'];
@ -106,6 +108,10 @@ class PaymentService extends BaseService
} }
} }
if ($accountGateway->isGateway(GATEWAY_WEPAY) && $transactionId = Session::get($invitation->id.'payment_ref')) {
$data['transaction_id'] = $transactionId;
}
return $data; return $data;
} }
@ -125,7 +131,7 @@ class PaymentService extends BaseService
$data['cvv'] = $input['cvv']; $data['cvv'] = $input['cvv'];
} }
if (isset($input['country_id'])) { if (isset($input['address1'])) {
$country = Country::find($input['country_id']); $country = Country::find($input['country_id']);
$data = array_merge($data, [ $data = array_merge($data, [
@ -216,7 +222,7 @@ class PaymentService extends BaseService
public function verifyClientPaymentMethod($client, $publicId, $amount1, $amount2) public function verifyClientPaymentMethod($client, $publicId, $amount1, $amount2)
{ {
$token = $client->getGatewayToken($accountGateway); $token = $client->getGatewayToken($accountGateway/* return parameter */, $accountGatewayToken/* return parameter */);
if ($accountGateway->gateway_id != GATEWAY_STRIPE) { if ($accountGateway->gateway_id != GATEWAY_STRIPE) {
return 'Unsupported gateway'; return 'Unsupported gateway';
} }
@ -232,7 +238,10 @@ class PaymentService extends BaseService
'amounts[]=' . intval($amount1) . '&amounts[]=' . intval($amount2) 'amounts[]=' . intval($amount1) . '&amounts[]=' . intval($amount2)
); );
if (!is_string($result)) { if (is_string($result)) {
return $result;
}
$paymentMethod->status = PAYMENT_METHOD_STATUS_VERIFIED; $paymentMethod->status = PAYMENT_METHOD_STATUS_VERIFIED;
$paymentMethod->save(); $paymentMethod->save();
@ -240,7 +249,7 @@ class PaymentService extends BaseService
$paymentMethod->account_gateway_token->default_payment_method_id = $paymentMethod->id; $paymentMethod->account_gateway_token->default_payment_method_id = $paymentMethod->id;
$paymentMethod->account_gateway_token->save(); $paymentMethod->account_gateway_token->save();
} }
}
return true; return true;
} }
@ -266,6 +275,17 @@ class PaymentService extends BaseService
if (!$response->isSuccessful()) { if (!$response->isSuccessful()) {
return $response->getMessage(); 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(); $paymentMethod->delete();
@ -291,16 +311,16 @@ class PaymentService extends BaseService
{ {
$customerReference = $client->getGatewayToken($accountGateway, $accountGatewayToken/* return paramenter */); $customerReference = $client->getGatewayToken($accountGateway, $accountGatewayToken/* return paramenter */);
if ($customerReference) { if ($customerReference && $customerReference != CUSTOMER_REFERENCE_LOCAL) {
$details['customerReference'] = $customerReference; $details['customerReference'] = $customerReference;
if ($accountGateway->gateway->id == GATEWAY_STRIPE) { if ($accountGateway->gateway_id == GATEWAY_STRIPE) {
$customerResponse = $gateway->fetchCustomer(array('customerReference' => $customerReference))->send(); $customerResponse = $gateway->fetchCustomer(array('customerReference' => $customerReference))->send();
if (!$customerResponse->isSuccessful()) { if (!$customerResponse->isSuccessful()) {
$customerReference = null; // The customer might not exist anymore $customerReference = null; // The customer might not exist anymore
} }
} elseif ($accountGateway->gateway->id == GATEWAY_BRAINTREE) { } elseif ($accountGateway->gateway_id == GATEWAY_BRAINTREE) {
$customer = $gateway->findCustomer($customerReference)->send()->getData(); $customer = $gateway->findCustomer($customerReference)->send()->getData();
if (!($customer instanceof \Braintree\Customer)) { if (!($customer instanceof \Braintree\Customer)) {
@ -309,7 +329,7 @@ class PaymentService extends BaseService
} }
} }
if ($accountGateway->gateway->id == GATEWAY_STRIPE) { if ($accountGateway->gateway_id == GATEWAY_STRIPE) {
if (!empty($details['plaidPublicToken'])) { if (!empty($details['plaidPublicToken'])) {
$plaidResult = $this->getPlaidToken($accountGateway, $details['plaidPublicToken'], $details['plaidAccountId']); $plaidResult = $this->getPlaidToken($accountGateway, $details['plaidPublicToken'], $details['plaidAccountId']);
@ -355,7 +375,7 @@ class PaymentService extends BaseService
return; return;
} }
} }
} elseif ($accountGateway->gateway->id == GATEWAY_BRAINTREE) { } elseif ($accountGateway->gateway_id == GATEWAY_BRAINTREE) {
if (!$customerReference) { if (!$customerReference) {
$tokenResponse = $gateway->createCustomer(array('customerData' => array()))->send(); $tokenResponse = $gateway->createCustomer(array('customerData' => array()))->send();
if ($tokenResponse->isSuccessful()) { if ($tokenResponse->isSuccessful()) {
@ -377,6 +397,38 @@ class PaymentService extends BaseService
return; return;
} }
} }
} elseif ($accountGateway->gateway_id == GATEWAY_WEPAY) {
$wepay = Utils::setupWePay($accountGateway);
try {
$wepay->request('credit_card/authorize', array(
'client_id' => WEPAY_CLIENT_ID,
'client_secret' => WEPAY_CLIENT_SECRET,
'credit_card_id' => intval($details['token']),
));
// Update the callback uri and get the card details
$wepay->request('credit_card/modify', array(
'client_id' => WEPAY_CLIENT_ID,
'client_secret' => WEPAY_CLIENT_SECRET,
'credit_card_id' => intval($details['token']),
'auto_update' => WEPAY_AUTO_UPDATE,
'callback_uri' => URL::to(env('WEBHOOK_PREFIX','').'paymenthook/'.$client->account->account_key.'/'.GATEWAY_WEPAY),
));
$tokenResponse = $wepay->request('credit_card', array(
'client_id' => WEPAY_CLIENT_ID,
'client_secret' => WEPAY_CLIENT_SECRET,
'credit_card_id' => intval($details['token']),
));
$customerReference = CUSTOMER_REFERENCE_LOCAL;
$sourceReference = $details['token'];
} catch (\WePayException $ex) {
$this->lastError = $ex->getMessage();
return;
}
} else {
return null;
} }
if ($customerReference) { if ($customerReference) {
@ -394,7 +446,7 @@ class PaymentService extends BaseService
$accountGatewayToken->token = $customerReference; $accountGatewayToken->token = $customerReference;
$accountGatewayToken->save(); $accountGatewayToken->save();
$paymentMethod = $this->createPaymentMethodFromGatewayResponse($tokenResponse, $accountGateway, $accountGatewayToken, $contactId); $paymentMethod = $this->convertPaymentMethodFromGatewayResponse($tokenResponse, $accountGateway, $accountGatewayToken, $contactId);
} else { } else {
$this->lastError = $tokenResponse->getMessage(); $this->lastError = $tokenResponse->getMessage();
@ -422,7 +474,7 @@ class PaymentService extends BaseService
$paymentMethod->setRelation('currency', $currency); $paymentMethod->setRelation('currency', $currency);
} }
} elseif ($source['object'] == 'card') { } elseif ($source['object'] == 'card') {
$paymentMethod->expiration = $source['exp_year'] . '-' . $source['exp_month'] . '-00'; $paymentMethod->expiration = $source['exp_year'] . '-' . $source['exp_month'] . '-01';
$paymentMethod->payment_type_id = $this->parseCardType($source['brand']); $paymentMethod->payment_type_id = $this->parseCardType($source['brand']);
} else { } else {
return null; return null;
@ -442,7 +494,7 @@ class PaymentService extends BaseService
if ($source instanceof \Braintree\CreditCard) { if ($source instanceof \Braintree\CreditCard) {
$paymentMethod->payment_type_id = $this->parseCardType($source->cardType); $paymentMethod->payment_type_id = $this->parseCardType($source->cardType);
$paymentMethod->last4 = $source->last4; $paymentMethod->last4 = $source->last4;
$paymentMethod->expiration = $source->expirationYear . '-' . $source->expirationMonth . '-00'; $paymentMethod->expiration = $source->expirationYear . '-' . $source->expirationMonth . '-01';
} elseif ($source instanceof \Braintree\PayPalAccount) { } elseif ($source instanceof \Braintree\PayPalAccount) {
$paymentMethod->email = $source->email; $paymentMethod->email = $source->email;
$paymentMethod->payment_type_id = PAYMENT_TYPE_ID_PAYPAL; $paymentMethod->payment_type_id = PAYMENT_TYPE_ID_PAYPAL;
@ -457,7 +509,23 @@ class PaymentService extends BaseService
return $paymentMethod; return $paymentMethod;
} }
public function createPaymentMethodFromGatewayResponse($gatewayResponse, $accountGateway, $accountGatewayToken = null, $contactId = null) { public function convertPaymentMethodFromWePay($source, $accountGatewayToken = null, $paymentMethod = null) {
// Creating a new one or updating an existing one
if (!$paymentMethod) {
$paymentMethod = $accountGatewayToken ? PaymentMethod::createNew($accountGatewayToken) : new PaymentMethod();
}
$paymentMethod->payment_type_id = $this->parseCardType($source->credit_card_name);
$paymentMethod->last4 = $source->last_four;
$paymentMethod->expiration = $source->expiration_year . '-' . $source->expiration_month . '-01';
$paymentMethod->setRelation('payment_type', Cache::get('paymentTypes')->find($paymentMethod->payment_type_id));
$paymentMethod->source_reference = $source->credit_card_id;
return $paymentMethod;
}
public function convertPaymentMethodFromGatewayResponse($gatewayResponse, $accountGateway, $accountGatewayToken = null, $contactId = null, $existingPaymentMethod = null) {
if ($accountGateway->gateway_id == GATEWAY_STRIPE) { if ($accountGateway->gateway_id == GATEWAY_STRIPE) {
$data = $gatewayResponse->getData(); $data = $gatewayResponse->getData();
if (!empty($data['object']) && ($data['object'] == 'card' || $data['object'] == 'bank_account')) { if (!empty($data['object']) && ($data['object'] == 'card' || $data['object'] == 'bank_account')) {
@ -470,7 +538,7 @@ class PaymentService extends BaseService
} }
if ($source) { if ($source) {
$paymentMethod = $this->convertPaymentMethodFromStripe($source, $accountGatewayToken); $paymentMethod = $this->convertPaymentMethodFromStripe($source, $accountGatewayToken, $existingPaymentMethod);
} }
} elseif ($accountGateway->gateway_id == GATEWAY_BRAINTREE) { } elseif ($accountGateway->gateway_id == GATEWAY_BRAINTREE) {
$data = $gatewayResponse->getData(); $data = $gatewayResponse->getData();
@ -478,11 +546,16 @@ class PaymentService extends BaseService
if (!empty($data->transaction)) { if (!empty($data->transaction)) {
$transaction = $data->transaction; $transaction = $data->transaction;
if ($existingPaymentMethod) {
$paymentMethod = $existingPaymentMethod;
} else {
$paymentMethod = $accountGatewayToken ? PaymentMethod::createNew($accountGatewayToken) : new PaymentMethod(); $paymentMethod = $accountGatewayToken ? PaymentMethod::createNew($accountGatewayToken) : new PaymentMethod();
}
if ($transaction->paymentInstrumentType == 'credit_card') { if ($transaction->paymentInstrumentType == 'credit_card') {
$card = $transaction->creditCardDetails; $card = $transaction->creditCardDetails;
$paymentMethod->last4 = $card->last4; $paymentMethod->last4 = $card->last4;
$paymentMethod->expiration = $card->expirationYear . '-' . $card->expirationMonth . '-00'; $paymentMethod->expiration = $card->expirationYear . '-' . $card->expirationMonth . '-01';
$paymentMethod->payment_type_id = $this->parseCardType($card->cardType); $paymentMethod->payment_type_id = $this->parseCardType($card->cardType);
} elseif ($transaction->paymentInstrumentType == 'paypal_account') { } elseif ($transaction->paymentInstrumentType == 'paypal_account') {
$paymentMethod->payment_type_id = PAYMENT_TYPE_ID_PAYPAL; $paymentMethod->payment_type_id = PAYMENT_TYPE_ID_PAYPAL;
@ -490,9 +563,20 @@ class PaymentService extends BaseService
} }
$paymentMethod->setRelation('payment_type', Cache::get('paymentTypes')->find($paymentMethod->payment_type_id)); $paymentMethod->setRelation('payment_type', Cache::get('paymentTypes')->find($paymentMethod->payment_type_id));
} elseif (!empty($data->paymentMethod)) { } elseif (!empty($data->paymentMethod)) {
$paymentMethod = $this->convertPaymentMethodFromBraintree($data->paymentMethod, $accountGatewayToken); $paymentMethod = $this->convertPaymentMethodFromBraintree($data->paymentMethod, $accountGatewayToken, $existingPaymentMethod);
} }
} elseif ($accountGateway->gateway_id == GATEWAY_WEPAY) {
if ($gatewayResponse instanceof \Omnipay\WePay\Message\CustomCheckoutResponse) {
$wepay = \Utils::setupWePay($accountGateway);
$gatewayResponse = $wepay->request('credit_card', array(
'client_id' => WEPAY_CLIENT_ID,
'client_secret' => WEPAY_CLIENT_SECRET,
'credit_card_id' => $gatewayResponse->getData()['payment_method']['credit_card']['id'],
));
}
$paymentMethod = $this->convertPaymentMethodFromWePay($gatewayResponse, $accountGatewayToken, $existingPaymentMethod);
} }
if (!empty($paymentMethod) && $accountGatewayToken && $contactId) { if (!empty($paymentMethod) && $accountGatewayToken && $contactId) {
@ -566,44 +650,50 @@ class PaymentService extends BaseService
$payment->payment_type_id = $this->detectCardType($card->getNumber()); $payment->payment_type_id = $this->detectCardType($card->getNumber());
} }
$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) { if ($accountGateway->gateway_id == GATEWAY_STRIPE) {
$data = $purchaseResponse->getData(); $data = $purchaseResponse->getData();
$source = !empty($data['source'])?$data['source']:$data['card'];
$payment->payment_status_id = $data['status'] == 'succeeded' ? PAYMENT_STATUS_COMPLETED : PAYMENT_STATUS_PENDING; $payment->payment_status_id = $data['status'] == 'succeeded' ? PAYMENT_STATUS_COMPLETED : PAYMENT_STATUS_PENDING;
}
if ($source) { if ($paymentMethod) {
$payment->last4 = $source['last4']; if ($paymentMethod->last4) {
$payment->last4 = $paymentMethod->last4;
}
if ($source['object'] == 'bank_account') { if ($paymentMethod->expiration) {
$payment->routing_number = $source['routing_number']; $payment->expiration = $paymentMethod->expiration;
$payment->payment_type_id = PAYMENT_TYPE_ACH;
} }
else{
$payment->expiration = $source['exp_year'] . '-' . $source['exp_month'] . '-00'; if ($paymentMethod->routing_number) {
$payment->payment_type_id = $this->parseCardType($source['brand']); $payment->routing_number = $paymentMethod->routing_number;
} }
if ($paymentMethod->payment_type_id) {
$payment->payment_type_id = $paymentMethod->payment_type_id;
} }
} elseif ($accountGateway->gateway_id == GATEWAY_BRAINTREE) {
$transaction = $purchaseResponse->getData()->transaction; if ($paymentMethod->email) {
if ($transaction->paymentInstrumentType == 'credit_card') { $payment->email = $paymentMethod->email;
$card = $transaction->creditCardDetails;
$payment->last4 = $card->last4;
$payment->expiration = $card->expirationYear . '-' . $card->expirationMonth . '-00';
$payment->payment_type_id = $this->parseCardType($card->cardType);
} elseif ($transaction->paymentInstrumentType == 'paypal_account') {
$payment->payment_type_id = PAYMENT_TYPE_ID_PAYPAL;
$payment->email = $transaction->paypalDetails->payerEmail;
}
} }
if ($payerId) { if ($payerId) {
$payment->payer_id = $payerId; $payment->payer_id = $payerId;
} }
if ($paymentMethod) { if ($savePaymentMethod) {
$payment->payment_method_id = $paymentMethod->id; $payment->payment_method_id = $paymentMethod->id;
} }
}
$payment->save(); $payment->save();
@ -665,20 +755,29 @@ class PaymentService extends BaseService
private function parseCardType($cardName) { private function parseCardType($cardName) {
$cardTypes = array( $cardTypes = array(
'Visa' => PAYMENT_TYPE_VISA, 'visa' => PAYMENT_TYPE_VISA,
'American Express' => PAYMENT_TYPE_AMERICAN_EXPRESS, 'americanexpress' => PAYMENT_TYPE_AMERICAN_EXPRESS,
'MasterCard' => PAYMENT_TYPE_MASTERCARD, 'amex' => PAYMENT_TYPE_AMERICAN_EXPRESS,
'Discover' => PAYMENT_TYPE_DISCOVER, 'mastercard' => PAYMENT_TYPE_MASTERCARD,
'JCB' => PAYMENT_TYPE_JCB, 'discover' => PAYMENT_TYPE_DISCOVER,
'Diners Club' => PAYMENT_TYPE_DINERS, 'jcb' => PAYMENT_TYPE_JCB,
'Carte Blanche' => PAYMENT_TYPE_CARTE_BLANCHE, 'dinersclub' => PAYMENT_TYPE_DINERS,
'China UnionPay' => PAYMENT_TYPE_UNIONPAY, 'carteblanche' => PAYMENT_TYPE_CARTE_BLANCHE,
'Laser' => PAYMENT_TYPE_LASER, 'chinaunionpay' => PAYMENT_TYPE_UNIONPAY,
'Maestro' => PAYMENT_TYPE_MAESTRO, 'unionpay' => PAYMENT_TYPE_UNIONPAY,
'Solo' => PAYMENT_TYPE_SOLO, 'laser' => PAYMENT_TYPE_LASER,
'Switch' => PAYMENT_TYPE_SWITCH '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])) { if (!empty($cardTypes[$cardName])) {
return $cardTypes[$cardName]; return $cardTypes[$cardName];
} else { } else {
@ -725,6 +824,11 @@ class PaymentService extends BaseService
$invitation = $invoice->invitations->first(); $invitation = $invoice->invitations->first();
$token = $client->getGatewayToken($accountGateway/* return parameter */, $accountGatewayToken/* return parameter */); $token = $client->getGatewayToken($accountGateway/* return parameter */, $accountGatewayToken/* return parameter */);
if (!$accountGatewayToken) {
return false;
}
$defaultPaymentMethod = $accountGatewayToken->default_payment_method; $defaultPaymentMethod = $accountGatewayToken->default_payment_method;
if (!$invitation || !$token || !$defaultPaymentMethod) { if (!$invitation || !$token || !$defaultPaymentMethod) {
@ -736,10 +840,9 @@ class PaymentService extends BaseService
$details = $this->getPaymentDetails($invitation, $accountGateway); $details = $this->getPaymentDetails($invitation, $accountGateway);
$details['customerReference'] = $token; $details['customerReference'] = $token;
if ($accountGateway->gateway_id == GATEWAY_STRIPE) { $details['token'] = $defaultPaymentMethod->source_reference;
$details['cardReference'] = $defaultPaymentMethod->source_reference; if ($accountGateway->gateway_id == GATEWAY_WEPAY) {
} elseif ($accountGateway->gateway_id == GATEWAY_BRAINTREE) { $details['transaction_id'] = 'autobill_'.$invoice->id;
$details['paymentMethodToken'] = $defaultPaymentMethod->source_reference;
} }
// submit purchase/get response // submit purchase/get response
@ -948,6 +1051,8 @@ class PaymentService extends BaseService
if ($payment->payment_type_id != PAYMENT_TYPE_CREDIT) { if ($payment->payment_type_id != PAYMENT_TYPE_CREDIT) {
$gateway = $this->createGateway($accountGateway); $gateway = $this->createGateway($accountGateway);
if ($accountGateway->gateway_id != GATEWAY_WEPAY) {
$refund = $gateway->refund(array( $refund = $gateway->refund(array(
'transactionReference' => $payment->transaction_reference, 'transactionReference' => $payment->transaction_reference,
'amount' => $amount, 'amount' => $amount,
@ -984,6 +1089,39 @@ class PaymentService extends BaseService
return false; return false;
} }
} }
} else {
$wepay = \Utils::setupWePay($accountGateway);
try {
$wepay->request('checkout/refund', array(
'checkout_id' => intval($payment->transaction_reference),
'refund_reason' => 'Refund issued by merchant.',
'amount' => $amount,
));
$payment->recordRefund($amount);
} catch (\WePayException $ex) {
if ($ex->getCode() == 4004) {
if ($amount == $payment->amount) {
try {
// This is an uncaptured transaction; try to cancel it
$wepay->request('checkout/cancel', array(
'checkout_id' => intval($payment->transaction_reference),
'cancel_reason' => 'Refund issued by merchant.',
));
$payment->markVoided();
} catch (\WePayException $ex) {
$this->error('Unknown', $ex->getMessage(), $accountGateway);
}
} else {
$this->error('Unknown', 'Partial refund not allowed for unsettled transactions.', $accountGateway);
return false;
}
} else {
$this->error('Unknown', $ex->getMessage(), $accountGateway);
}
}
}
} else { } else {
$payment->recordRefund($amount); $payment->recordRefund($amount);
} }

View File

@ -74,7 +74,8 @@
"league/flysystem-rackspace": "~1.0", "league/flysystem-rackspace": "~1.0",
"barracudanetworks/archivestream-php": "^1.0", "barracudanetworks/archivestream-php": "^1.0",
"omnipay/braintree": "~2.0@dev", "omnipay/braintree": "~2.0@dev",
"gatepay/FedACHdir": "dev-master@dev" "gatepay/FedACHdir": "dev-master@dev",
"wepay/php-sdk": "^0.2"
}, },
"require-dev": { "require-dev": {
"phpunit/phpunit": "~4.0", "phpunit/phpunit": "~4.0",

55
composer.lock generated
View File

@ -4,8 +4,8 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"hash": "7139e4aedb2ac151079c50ee5c17f93c", "hash": "b2471aea1af5ef67a1379ad95b5138f7",
"content-hash": "a314d6c0a16785dd2395a7fd73cdc76d", "content-hash": "df30a311df0341933d4ff2c3aa5974a6",
"packages": [ "packages": [
{ {
"name": "agmscode/omnipay-agms", "name": "agmscode/omnipay-agms",
@ -505,7 +505,7 @@
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/barryvdh/laravel-ide-helper/zipball/e97ed532f09e290b91ff7713b785ed7ab11d0812", "url": "https://api.github.com/repos/barryvdh/laravel-ide-helper/zipball/f7b31bdbdceaaea930c71df20e4180b0b7172b4a",
"reference": "e97ed532f09e290b91ff7713b785ed7ab11d0812", "reference": "e97ed532f09e290b91ff7713b785ed7ab11d0812",
"shasum": "" "shasum": ""
}, },
@ -2785,7 +2785,7 @@
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/labs7in0/omnipay-wechat/zipball/40c9f86df6573ad98ae1dd0d29712ccbc789a74e", "url": "https://api.github.com/repos/labs7in0/omnipay-wechat/zipball/c8d80c3b48bae2bab071f283f75b1cd8624ed3c7",
"reference": "40c9f86df6573ad98ae1dd0d29712ccbc789a74e", "reference": "40c9f86df6573ad98ae1dd0d29712ccbc789a74e",
"shasum": "" "shasum": ""
}, },
@ -8057,6 +8057,53 @@
], ],
"time": "2016-02-25 10:29:59" "time": "2016-02-25 10:29:59"
}, },
{
"name": "wepay/php-sdk",
"version": "0.2.7",
"source": {
"type": "git",
"url": "https://github.com/wepay/PHP-SDK.git",
"reference": "31bfcdd97d2c9c33c9c09129638ae31840822182"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/wepay/PHP-SDK/zipball/31bfcdd97d2c9c33c9c09129638ae31840822182",
"reference": "31bfcdd97d2c9c33c9c09129638ae31840822182",
"shasum": ""
},
"require": {
"ext-curl": "*",
"php": ">=5.3.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "0.2.x-dev"
}
},
"autoload": {
"classmap": [
"wepay.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"Apache-2.0"
],
"authors": [
{
"name": "WePay",
"email": "api@wepay.com"
}
],
"description": "WePay APIv2 SDK for PHP",
"keywords": [
"payment",
"sdk",
"wepay"
],
"time": "2015-08-14 19:42:37"
},
{ {
"name": "wildbit/laravel-postmark-provider", "name": "wildbit/laravel-postmark-provider",
"version": "3.0.0", "version": "3.0.0",

View File

@ -78,6 +78,11 @@ class PaymentsChanges extends Migration
->where('auto_bill', '=', 1) ->where('auto_bill', '=', 1)
->update(array('client_enable_auto_bill' => 1, 'auto_bill' => AUTO_BILL_OPT_OUT)); ->update(array('client_enable_auto_bill' => 1, 'auto_bill' => AUTO_BILL_OPT_OUT));
\DB::table('invoices')
->where('auto_bill', '=', 0)
->where('is_recurring', '=', 1)
->update(array('auto_bill' => AUTO_BILL_OFF));
Schema::table('account_gateway_tokens', function($table) Schema::table('account_gateway_tokens', function($table)
{ {
@ -113,11 +118,15 @@ class PaymentsChanges extends Migration
$table->dropColumn('payment_method_id'); $table->dropColumn('payment_method_id');
}); });
\DB::table('invoices')
->where('auto_bill', '=', AUTO_BILL_OFF)
->update(array('auto_bill' => 0));
\DB::table('invoices') \DB::table('invoices')
->where(function($query){ ->where(function($query){
$query->where('auto_bill', '=', AUTO_BILL_ALWAYS); $query->where('auto_bill', '=', AUTO_BILL_ALWAYS);
$query->orwhere(function($query){ $query->orwhere(function($query){
$query->where('auto_bill', '!=', AUTO_BILL_OFF); $query->where('auto_bill', '!=', 0);
$query->where('client_enable_auto_bill', '=', 1); $query->where('client_enable_auto_bill', '=', 1);
}); });
}) })

View File

@ -30933,7 +30933,7 @@ function truncate(string, length){
// Show/hide the 'Select' option in the datalists // Show/hide the 'Select' option in the datalists
function actionListHandler() { function actionListHandler() {
$('tbody tr').mouseover(function() { $('tbody tr .tr-action').closest('tr').mouseover(function() {
$(this).closest('tr').find('.tr-action').show(); $(this).closest('tr').find('.tr-action').show();
$(this).closest('tr').find('.tr-status').hide(); $(this).closest('tr').find('.tr-status').hide();
}).mouseout(function() { }).mouseout(function() {

View File

@ -1042,7 +1042,7 @@ function truncate(string, length){
// Show/hide the 'Select' option in the datalists // Show/hide the 'Select' option in the datalists
function actionListHandler() { function actionListHandler() {
$('tbody tr').mouseover(function() { $('tbody tr .tr-action').closest('tr').mouseover(function() {
$(this).closest('tr').find('.tr-action').show(); $(this).closest('tr').find('.tr-action').show();
$(this).closest('tr').find('.tr-status').hide(); $(this).closest('tr').find('.tr-status').hide();
}).mouseout(function() { }).mouseout(function() {

View File

@ -1282,6 +1282,26 @@ $LANG = array(
'import_ofx' => 'Import OFX', 'import_ofx' => 'Import OFX',
'ofx_file' => 'OFX File', 'ofx_file' => 'OFX File',
'ofx_parse_failed' => 'Failed to parse OFX file', 'ofx_parse_failed' => 'Failed to parse OFX file',
// WePay
'wepay' => 'WePay',
'sign_up_with_wepay' => 'Sign up with WePay',
'use_another_provider' => 'Use another provider',
'company_name' => 'Company Name',
'wepay_company_name_help' => 'This will appear on client\'s credit card statements.',
'wepay_description_help' => 'The purpose of this account.',
'wepay_tos_agree' => 'I agree to the :link.',
'wepay_tos_link_text' => 'WePay Terms of Service',
'resend_confirmation_email' => 'Resend Confirmation Email',
'manage_wepay_account' => 'Manage WePay Account',
'action_required' => 'Action Required',
'finish_setup' => 'Finish Setup',
'created_wepay_confirmation_required' => 'Please check your email and confirm your email address with WePay.',
'switch_to_wepay' => 'Switch to WePay',
'switch' => 'Switch',
'restore_account_gateway' => 'Restore Gateway',
'restored_account_gateway' => 'Successfully restored gateway',
); );
return $LANG; return $LANG;

View File

@ -5,15 +5,18 @@
@include('accounts.nav', ['selected' => ACCOUNT_PAYMENTS]) @include('accounts.nav', ['selected' => ACCOUNT_PAYMENTS])
{!! Former::open($url)->method($method)->rule()->addClass('warn-on-exit') !!} @if(!$accountGateway && WEPAY_CLIENT_ID && !$account->getGatewayByType(PAYMENT_TYPE_CREDIT_CARD) && !$account->getGatewayByType(PAYMENT_TYPE_STRIPE))
{!! Former::populateField('token_billing_type_id', $account->token_billing_type_id) !!} @include('accounts.partials.account_gateway_wepay')
@endif
<div id="other-providers">
<div class="panel panel-default"> <div class="panel panel-default">
<div class="panel-heading"> <div class="panel-heading">
<h3 class="panel-title">{!! trans($title) !!}</h3> <h3 class="panel-title">{!! trans($title) !!}</h3>
</div> </div>
<div class="panel-body form-padding-right"> <div class="panel-body form-padding-right">
{!! Former::open($url)->method($method)->rule()->addClass('warn-on-exit') !!}
{!! Former::populateField('token_billing_type_id', $account->token_billing_type_id) !!}
@if ($accountGateway) @if ($accountGateway)
{!! Former::populateField('gateway_id', $accountGateway->gateway_id) !!} {!! Former::populateField('gateway_id', $accountGateway->gateway_id) !!}
@ -91,7 +94,7 @@
{!! Former::text('publishable_key') !!} {!! Former::text('publishable_key') !!}
@endif @endif
@if ($gateway->id == GATEWAY_STRIPE || $gateway->id == GATEWAY_BRAINTREE) @if ($gateway->id == GATEWAY_STRIPE || $gateway->id == GATEWAY_BRAINTREE || $gateway->id == GATEWAY_WEPAY)
{!! Former::select('token_billing_type_id') {!! Former::select('token_billing_type_id')
->options($tokenBillingOptions) ->options($tokenBillingOptions)
->help(trans('texts.token_billing_help')) !!} ->help(trans('texts.token_billing_help')) !!}
@ -101,7 +104,7 @@
<div class="form-group"> <div class="form-group">
<label class="control-label col-lg-4 col-sm-4">{{ trans('texts.webhook_url') }}</label> <label class="control-label col-lg-4 col-sm-4">{{ trans('texts.webhook_url') }}</label>
<div class="col-lg-8 col-sm-8 help-block"> <div class="col-lg-8 col-sm-8 help-block">
<input type="text" class="form-control" onfocus="$(this).select()" readonly value="{{ URL::to('/paymenthook/'.$account->account_key.'/'.GATEWAY_STRIPE) }}"> <input type="text" class="form-control" onfocus="$(this).select()" readonly value="{{ URL::to(env('WEBHOOK_PREFIX','').'paymenthook/'.$account->account_key.'/'.GATEWAY_STRIPE) }}">
<div class="help-block"><strong>{!! trans('texts.stripe_webhook_help', [ <div class="help-block"><strong>{!! trans('texts.stripe_webhook_help', [
'link'=>'<a href="https://dashboard.stripe.com/account/webhooks" target="_blank">'.trans('texts.stripe_webhook_help_link_text').'</a>' 'link'=>'<a href="https://dashboard.stripe.com/account/webhooks" target="_blank">'.trans('texts.stripe_webhook_help_link_text').'</a>'
]) !!}</strong></div> ]) !!}</strong></div>
@ -173,7 +176,6 @@
</div> </div>
@endif @endif
</div> </div>
</div> </div>
</div> </div>
@ -183,6 +185,7 @@
$countGateways > 0 ? Button::normal(trans('texts.cancel'))->large()->asLinkTo(URL::to('/settings/online_payments'))->appendIcon(Icon::create('remove-circle')) : false, $countGateways > 0 ? Button::normal(trans('texts.cancel'))->large()->asLinkTo(URL::to('/settings/online_payments'))->appendIcon(Icon::create('remove-circle')) : false,
Button::success(trans('texts.save'))->submit()->large()->appendIcon(Icon::create('floppy-disk'))) !!} Button::success(trans('texts.save'))->submit()->large()->appendIcon(Icon::create('floppy-disk'))) !!}
{!! Former::close() !!} {!! Former::close() !!}
</div>
<script type="text/javascript"> <script type="text/javascript">
@ -256,6 +259,11 @@
enableUpdateAddress(); enableUpdateAddress();
$('#enable_ach').change(enablePlaidSettings) $('#enable_ach').change(enablePlaidSettings)
$('#show-other-providers').click(function(){
$(this).hide();
$('#other-providers').show();
})
}) })
</script> </script>

View File

@ -0,0 +1,7 @@
@extends('header')
@section('content')
@parent
@include('accounts.nav', ['selected' => ACCOUNT_PAYMENTS])
@include('accounts.partials.account_gateway_wepay')
@stop

View File

@ -0,0 +1,59 @@
{!! Former::open($url)->method($method)->rules(array(
'first_name' => 'required',
'last_name' => 'required',
'email' => 'required',
'description' => 'required',
'company_name' => 'required',
'tos_agree' => 'required',
))->addClass('warn-on-exit') !!}
{!! Former::populateField('company_name', $account->getDisplayName()) !!}
{!! Former::populateField('first_name', $user->first_name) !!}
{!! Former::populateField('last_name', $user->last_name) !!}
{!! Former::populateField('email', $user->email) !!}
{!! Former::populateField('show_address', 1) !!}
{!! Former::populateField('update_address', 1) !!}
{!! Former::populateField('token_billing_type_id', $account->token_billing_type_id) !!}
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">{!! trans('texts.online_payments') !!}</h3>
</div>
<div class="panel-body form-padding-right">
{!! Former::text('first_name') !!}
{!! Former::text('last_name') !!}
{!! Former::text('email') !!}
{!! Former::text('company_name')->help('wepay_company_name_help')->maxlength(255) !!}
{!! Former::text('description')->help('wepay_description_help') !!}
{!! Former::select('token_billing_type_id')
->options($tokenBillingOptions)
->help(trans('texts.token_billing_help')) !!}
{!! Former::checkbox('show_address')
->label(trans('texts.billing_address'))
->text(trans('texts.show_address_help'))
->addGroupClass('gateway-option') !!}
{!! Former::checkbox('update_address')
->label(' ')
->text(trans('texts.update_address_help'))
->addGroupClass('gateway-option') !!}
{!! Former::checkboxes('creditCardTypes[]')
->label('Accepted Credit Cards')
->checkboxes($creditCardTypes)
->class('creditcard-types')
->addGroupClass('gateway-option')
!!}
{!! Former::checkbox('tos_agree')->label(' ')->text(trans('texts.wepay_tos_agree',
['link'=>'<a href="https://go.wepay.com/terms-of-service-us" target="_blank">'.trans('texts.wepay_tos_link_text').'</a>']
))->value('true') !!}
<center>
{!! Button::primary(trans('texts.sign_up_with_wepay'))
->submit()
->large() !!}
@if(isset($gateways))
<br><br>
<a href="javascript::void" id="show-other-providers">{{ trans('texts.use_another_provider') }}</a>
@endif
</center>
</div>
</div>
<style>#other-providers{display:none}</style>
<input type="hidden" name="gateway_id" value="{{ GATEWAY_WEPAY }}">
{!! Former::close() !!}

View File

@ -4,6 +4,17 @@
@parent @parent
@include('accounts.nav', ['selected' => ACCOUNT_PAYMENTS]) @include('accounts.nav', ['selected' => ACCOUNT_PAYMENTS])
@if ($showSwitchToWepay)
{!! Button::success(trans('texts.switch_to_wepay'))
->asLinkTo(URL::to('/gateways/switch/wepay'))
->appendIcon(Icon::create('circle-arrow-up')) !!}
&nbsp;
@endif
<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) @if ($showAdd)
{!! Button::primary(trans('texts.add_gateway')) {!! Button::primary(trans('texts.add_gateway'))
->asLinkTo(URL::to('/gateways/create')) ->asLinkTo(URL::to('/gateways/create'))
@ -28,6 +39,14 @@
<script> <script>
window.onDatatableReady = actionListHandler; window.onDatatableReady = actionListHandler;
function setTrashVisible() {
var checked = $('#trashed').is(':checked');
var url = '{{ URL::to('view_archive/gateway') }}' + (checked ? '/true' : '/false');
$.get(url, function(data) {
refreshDatatable();
})
}
</script> </script>
@stop @stop

View File

@ -166,24 +166,24 @@
</span> </span>
@if($account->getTokenGatewayId()) @if($account->getTokenGatewayId())
<span data-bind="visible: is_recurring()" style="display: none"> <span data-bind="visible: is_recurring()" style="display: none">
<div data-bind="visible: !(auto_bill() == 1 &amp;&amp; client_enable_auto_bill()) &amp;&amp; !(auto_bill() == 2 &amp;&amp; !client_enable_auto_bill())" style="display: none"> <div data-bind="visible: !(auto_bill() == {{AUTO_BILL_OPT_IN}} &amp;&amp; client_enable_auto_bill()) &amp;&amp; !(auto_bill() == {{AUTO_BILL_OPT_OUT}} &amp;&amp; !client_enable_auto_bill())" style="display: none">
{!! Former::select('auto_bill') {!! Former::select('auto_bill')
->data_bind("value: auto_bill, valueUpdate: 'afterkeydown', event:{change:function(){if(auto_bill()==1)client_enable_auto_bill(0);if(auto_bill()==2)client_enable_auto_bill(1)}}") ->data_bind("value: auto_bill, valueUpdate: 'afterkeydown', event:{change:function(){if(auto_bill()==".AUTO_BILL_OPT_IN.")client_enable_auto_bill(0);if(auto_bill()==".AUTO_BILL_OPT_OUT.")client_enable_auto_bill(1)}}")
->options([ ->options([
0 => trans('texts.off'), AUTO_BILL_OFF => trans('texts.off'),
1 => trans('texts.opt_in'), AUTO_BILL_OPT_IN => trans('texts.opt_in'),
2 => trans('texts.opt_out'), AUTO_BILL_OPT_OUT => trans('texts.opt_out'),
3 => trans('texts.always'), AUTO_BILL_ALWAYS => trans('texts.always'),
]) !!} ]) !!}
</div> </div>
<input type="hidden" name="client_enable_auto_bill" data-bind="attr: { value: client_enable_auto_bill() }" /> <input type="hidden" name="client_enable_auto_bill" data-bind="attr: { value: client_enable_auto_bill() }" />
<div class="form-group" data-bind="visible: auto_bill() == 1 &amp;&amp; client_enable_auto_bill()"> <div class="form-group" data-bind="visible: auto_bill() == {{AUTO_BILL_OPT_IN}} &amp;&amp; client_enable_auto_bill()">
<div class="col-sm-4 control-label">{{trans('texts.auto_bill')}}</div> <div class="col-sm-4 control-label">{{trans('texts.auto_bill')}}</div>
<div class="col-sm-8" style="padding-top:10px;padding-bottom:9px"> <div class="col-sm-8" style="padding-top:10px;padding-bottom:9px">
{{trans('texts.opted_in')}} - <a href="#" data-bind="click:function(){client_enable_auto_bill(false)}">({{trans('texts.disable')}})</a> {{trans('texts.opted_in')}} - <a href="#" data-bind="click:function(){client_enable_auto_bill(false)}">({{trans('texts.disable')}})</a>
</div> </div>
</div> </div>
<div class="form-group" data-bind="visible: auto_bill() == 2 &amp;&amp; !client_enable_auto_bill()"> <div class="form-group" data-bind="visible: auto_bill() == {{AUTO_BILL_OPT_OUT}} &amp;&amp; !client_enable_auto_bill()">
<div class="col-sm-4 control-label">{{trans('texts.auto_bill')}}</div> <div class="col-sm-4 control-label">{{trans('texts.auto_bill')}}</div>
<div class="col-sm-8" style="padding-top:10px;padding-bottom:9px"> <div class="col-sm-8" style="padding-top:10px;padding-bottom:9px">
{{trans('texts.opted_out')}} - <a href="#" data-bind="click:function(){client_enable_auto_bill(true)}">({{trans('texts.enable')}})</a> {{trans('texts.opted_out')}} - <a href="#" data-bind="click:function(){client_enable_auto_bill(true)}">({{trans('texts.enable')}})</a>

View File

@ -3,221 +3,11 @@
@section('head') @section('head')
@parent @parent
@if (!empty($braintreeClientToken)) @if (!empty($braintreeClientToken))
<script type="text/javascript" src="https://js.braintreegateway.com/js/braintree-2.23.0.min.js"></script> @include('payments.tokenization_braintree')
<script type="text/javascript" >
$(function() {
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) {
var $form = $('.payment-form');
$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();
}
}
});
$('.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>
@elseif (isset($accountGateway) && $accountGateway->getPublishableStripeKey()) @elseif (isset($accountGateway) && $accountGateway->getPublishableStripeKey())
<script type="text/javascript" src="https://js.stripe.com/v2/"></script> @include('payments.tokenization_stripe')
<script type="text/javascript"> @elseif (isset($accountGateway) && $accountGateway->gateway_id == GATEWAY_WEPAY)
Stripe.setPublishableKey('{{ $accountGateway->getPublishableStripeKey() }}'); @include('payments.tokenization_wepay')
$(function() {
$('.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: $("#country").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="stripeToken"/>').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>
@else @else
<script type="text/javascript"> <script type="text/javascript">
$(function() { $(function() {
@ -232,7 +22,6 @@
}); });
</script> </script>
@endif @endif
@stop @stop
@section('content') @section('content')
@ -283,7 +72,6 @@
{{ Former::populateField('email', $contact->email) }} {{ Former::populateField('email', $contact->email) }}
@if (!$client->country_id && $client->account->country_id) @if (!$client->country_id && $client->account->country_id)
{{ Former::populateField('country_id', $client->account->country_id) }} {{ Former::populateField('country_id', $client->account->country_id) }}
{{ Former::populateField('country', $client->account->country->iso_3166_2) }}
@endif @endif
@if (!$client->currency_id && $client->account->currency_id) @if (!$client->currency_id && $client->account->currency_id)
{{ Former::populateField('currency_id', $client->account->currency_id) }} {{ Former::populateField('currency_id', $client->account->currency_id) }}
@ -351,7 +139,6 @@
<div class="col-md-6"> <div class="col-md-6">
{!! Former::text('first_name') {!! Former::text('first_name')
->placeholder(trans('texts.first_name')) ->placeholder(trans('texts.first_name'))
->autocomplete('given-name')
->label('') !!} ->label('') !!}
</div> </div>
<div class="col-md-6"> <div class="col-md-6">
@ -451,9 +238,9 @@
))->inline()->label(trans('texts.account_holder_type')); !!} ))->inline()->label(trans('texts.account_holder_type')); !!}
{!! Former::text('account_holder_name') {!! Former::text('account_holder_name')
->label(trans('texts.account_holder_name')) !!} ->label(trans('texts.account_holder_name')) !!}
{!! Former::select('country') {!! Former::select('country_id')
->label(trans('texts.country_id')) ->label(trans('texts.country_id'))
->fromQuery($countries, 'name', 'iso_3166_2') ->fromQuery($countries, 'name', 'id')
->addGroupClass('country-select') !!} ->addGroupClass('country-select') !!}
{!! Former::select('currency') {!! Former::select('currency')
->label(trans('texts.currency_id')) ->label(trans('texts.currency_id'))
@ -473,11 +260,10 @@
{!! Former::text('') {!! Former::text('')
->id('confirm_account_number') ->id('confirm_account_number')
->label(trans('texts.confirm_account_number')) !!} ->label(trans('texts.confirm_account_number')) !!}
</div>
{!! Former::checkbox('authorize_ach') {!! Former::checkbox('authorize_ach')
->text(trans('texts.ach_authorization', ['company'=>$account->getDisplayName()])) ->text(trans('texts.ach_authorization', ['company'=>$account->getDisplayName()]))
->label(' ') !!} ->label(' ') !!}
</div>
</div>
<div class="col-md-8 col-md-offset-4"> <div class="col-md-8 col-md-offset-4">
{!! Button::success(strtoupper(trans('texts.add_account'))) {!! Button::success(strtoupper(trans('texts.add_account')))
->submit() ->submit()
@ -494,7 +280,7 @@
<h3>{{ trans('texts.paypal') }}</h3> <h3>{{ trans('texts.paypal') }}</h3>
<div>{{$paypalDetails->firstName}} {{$paypalDetails->lastName}}</div> <div>{{$paypalDetails->firstName}} {{$paypalDetails->lastName}}</div>
<div>{{$paypalDetails->email}}</div> <div>{{$paypalDetails->email}}</div>
<input type="hidden" name="payment_method_nonce" value="{{$sourceId}}"> <input type="hidden" name="sourceToken" value="{{$sourceId}}">
<input type="hidden" name="first_name" value="{{$paypalDetails->firstName}}"> <input type="hidden" name="first_name" value="{{$paypalDetails->firstName}}">
<input type="hidden" name="last_name" value="{{$paypalDetails->lastName}}"> <input type="hidden" name="last_name" value="{{$paypalDetails->lastName}}">
<input type="hidden" name="email" value="{{$paypalDetails->email}}"> <input type="hidden" name="email" value="{{$paypalDetails->email}}">
@ -524,7 +310,7 @@
@if (!empty($braintreeClientToken)) @if (!empty($braintreeClientToken))
<div id="card_number" class="braintree-hosted form-control"></div> <div id="card_number" class="braintree-hosted form-control"></div>
@else @else
{!! Former::text($accountGateway->getPublishableStripeKey() ? '' : 'card_number') {!! Former::text(!empty($tokenize) ? '' : 'card_number')
->id('card_number') ->id('card_number')
->placeholder(trans('texts.card_number')) ->placeholder(trans('texts.card_number'))
->autocomplete('cc-number') ->autocomplete('cc-number')
@ -535,7 +321,7 @@
@if (!empty($braintreeClientToken)) @if (!empty($braintreeClientToken))
<div id="cvv" class="braintree-hosted form-control"></div> <div id="cvv" class="braintree-hosted form-control"></div>
@else @else
{!! Former::text($accountGateway->getPublishableStripeKey() ? '' : 'cvv') {!! Former::text(!empty($tokenize) ? '' : 'cvv')
->id('cvv') ->id('cvv')
->placeholder(trans('texts.cvv')) ->placeholder(trans('texts.cvv'))
->autocomplete('off') ->autocomplete('off')
@ -548,7 +334,7 @@
@if (!empty($braintreeClientToken)) @if (!empty($braintreeClientToken))
<div id="expiration_month" class="braintree-hosted form-control"></div> <div id="expiration_month" class="braintree-hosted form-control"></div>
@else @else
{!! Former::select($accountGateway->getPublishableStripeKey() ? '' : 'expiration_month') {!! Former::select(!empty($tokenize) ? '' : 'expiration_month')
->id('expiration_month') ->id('expiration_month')
->autocomplete('cc-exp-month') ->autocomplete('cc-exp-month')
->placeholder(trans('texts.expiration_month')) ->placeholder(trans('texts.expiration_month'))
@ -571,7 +357,7 @@
@if (!empty($braintreeClientToken)) @if (!empty($braintreeClientToken))
<div id="expiration_year" class="braintree-hosted form-control"></div> <div id="expiration_year" class="braintree-hosted form-control"></div>
@else @else
{!! Former::select($accountGateway->getPublishableStripeKey() ? '' : 'expiration_year') {!! Former::select(!empty($tokenize) ? '' : 'expiration_year')
->id('expiration_year') ->id('expiration_year')
->autocomplete('cc-exp-year') ->autocomplete('cc-exp-year')
->placeholder(trans('texts.expiration_year')) ->placeholder(trans('texts.expiration_year'))
@ -662,7 +448,7 @@
$('#routing_number, #country').on('change keypress keyup keydown paste', function(){setTimeout(function () { $('#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, ''); var routingNumber = $('#routing_number').val().replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, '');
if (routingNumber.length != 9 || $("#country").val() != 'US' || routingNumberCache[routingNumber] === false) { if (routingNumber.length != 9 || $("#country_id").val() != 840 || routingNumberCache[routingNumber] === false) {
$('#bank_name').hide(); $('#bank_name').hide();
} else if (routingNumberCache[routingNumber]) { } else if (routingNumberCache[routingNumber]) {
$('#bank_name').empty().append(routingNumberCache[routingNumber]).show(); $('#bank_name').empty().append(routingNumberCache[routingNumber]).show();

View File

@ -60,7 +60,7 @@
@endif @endif
@if($paymentMethod->payment_type_id == PAYMENT_TYPE_ACH) @if($paymentMethod->payment_type_id == PAYMENT_TYPE_ACH)
@if($paymentMethod->bank_data) @if($paymentMethod->bank_data)
{{ $paymentMethod->bank_data }} {{ $paymentMethod->bank_data->name }}
@endif @endif
@if($paymentMethod->status == PAYMENT_METHOD_STATUS_NEW) @if($paymentMethod->status == PAYMENT_METHOD_STATUS_NEW)
<a href="#" onclick="completeVerification('{{$paymentMethod->public_id}}','{{$paymentMethod->currency->symbol}}')">({{trans('texts.complete_verification')}})</a> <a href="#" onclick="completeVerification('{{$paymentMethod->public_id}}','{{$paymentMethod->currency->symbol}}')">({{trans('texts.complete_verification')}})</a>
@ -183,5 +183,6 @@
function setDefault(sourceId) { function setDefault(sourceId) {
$('#default_id').val(sourceId); $('#default_id').val(sourceId);
$('#defaultSourceForm').submit() $('#defaultSourceForm').submit()
return false;
} }
</script> </script>

View File

@ -0,0 +1,67 @@
<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

@ -0,0 +1,154 @@
<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,62 @@
<script type="text/javascript" src="https://static.wepay.com/min/js/tokenization.v2.js"></script>
<script type="text/javascript">
$(function() {
var countries = {!! $countries->pluck('iso_3166_2','id') !!};
WePay.set_endpoint('{{ WEPAY_ENVIRONMENT }}');
var $form = $('.payment-form');
$('.payment-form').submit(function(event) {
var data = {
client_id: {{ WEPAY_CLIENT_ID }},
user_name: $('#first_name').val() + ' ' + $('#last_name').val(),
email: $('#email').val(),
cc_number: $('#card_number').val(),
cvv: $('#cvv').val(),
expiration_month: $('#expiration_month').val(),
expiration_year: $('#expiration_year').val(),
address: {
address1: $('#address1').val(),
address2: $('#address2').val(),
city: $('#city').val(),
country: countries[$("#country_id").val()]
}
};
if(data.address.country == 'US') {
data.address.zip = $('#postal_code').val();
} else {
data.address.postcode = $('#postal_code').val();
}
// Not including state/province, since WePay wants 2-letter codes and users enter the full name
// Disable the submit button to prevent repeated clicks
$form.find('button').prop('disabled', true);
$('#js-error-message').hide();
var response = WePay.credit_card.create(data, function(response) {
if (response.error) {
// Show the errors on the form
var error = response.error_description;
$form.find('button').prop('disabled', false);
$('#js-error-message').text(error).fadeIn();
} else {
// response contains id and card, which contains additional card details
var token = response.credit_card_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();
}
});
if (response.error) {
// Show the errors on the form
var error = response.error_description;
$form.find('button').prop('disabled', false);
$('#js-error-message').text(error).fadeIn();
}
// Prevent the form from submitting with the default action
return false;
});
});
</script>