diff --git a/.env.example b/.env.example index 41ce2039f7e5..dd797f50d0ec 100644 --- a/.env.example +++ b/.env.example @@ -67,4 +67,12 @@ API_SECRET=password #MAX_DOCUMENT_SIZE # KB #MAX_EMAIL_DOCUMENTS_SIZE # Total KB #MAX_ZIP_DOCUMENTS_SIZE # Total KB (uncompressed) -#DOCUMENT_PREVIEW_SIZE # Pixels \ No newline at end of file +#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"}')); \ No newline at end of file diff --git a/app/Http/Controllers/AccountController.php b/app/Http/Controllers/AccountController.php index 3abaaadfd78c..be3ee331f403 100644 --- a/app/Http/Controllers/AccountController.php +++ b/app/Http/Controllers/AccountController.php @@ -1,5 +1,6 @@ account; $account->load('account_gateways'); $count = count($account->account_gateways); + $trashedCount = AccountGateway::scope($account->id)->withTrashed()->count(); if ($accountGateway = $account->getGatewayConfig(GATEWAY_STRIPE)) { if (! $accountGateway->getPublishableStripeKey()) { @@ -419,10 +421,17 @@ class AccountController extends BaseController } } - if ($count == 0) { + if ($trashedCount == 0) { return Redirect::to('gateways/create'); } 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', [ + 'showSwitchToWepay' => $switchToWepay, 'showAdd' => $count < count(Gateway::$paymentTypes), 'title' => trans('texts.online_payments') ]); diff --git a/app/Http/Controllers/AccountGatewayController.php b/app/Http/Controllers/AccountGatewayController.php index a4fd57c7e5f4..fef9a511636e 100644 --- a/app/Http/Controllers/AccountGatewayController.php +++ b/app/Http/Controllers/AccountGatewayController.php @@ -11,6 +11,7 @@ use Validator; use stdClass; use URL; use Utils; +use WePay; use App\Models\Gateway; use App\Models\Account; use App\Models\AccountGateway; @@ -57,7 +58,6 @@ class AccountGatewayController extends BaseController $data['paymentTypeId'] = $accountGateway->getPaymentType(); $data['selectGateways'] = Gateway::where('id', '=', $accountGateway->gateway_id)->get(); - return View::make('accounts.account_gateway', $data); } @@ -101,35 +101,12 @@ class AccountGatewayController extends BaseController private function getViewModel($accountGateway = false) { $selectedCards = $accountGateway ? $accountGateway->accepted_credit_cards : 0; - $account = Auth::user()->account; + $user = Auth::user(); + $account =$user->account; $paymentTypes = []; foreach (Gateway::$paymentTypes as $type) { - if ($accountGateway || !$account->getGatewayByType($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; - } - } - + if ($accountGateway || $account->canAddGateway($type)) { $paymentTypes[$type] = $type == PAYMENT_TYPE_CREDIT_CARD ? trans('texts.other_providers'): trans('texts.'.strtolower($type)); if ($type == PAYMENT_TYPE_BITCOIN) { @@ -158,7 +135,7 @@ class AccountGatewayController extends BaseController foreach ($gateways as $gateway) { $fields = $gateway->getFields(); asort($fields); - $gateway->fields = $fields; + $gateway->fields = $gateway->id == GATEWAY_WEPAY ? [] : $fields; if ($accountGateway && $accountGateway->gateway_id == $gateway->id) { $accountGateway->fields = $gateway->fields; } @@ -172,6 +149,7 @@ class AccountGatewayController extends BaseController return [ 'paymentTypes' => $paymentTypes, 'account' => $account, + 'user' => $user, 'accountGateway' => $accountGateway, 'config' => false, 'gateways' => $gateways, @@ -188,7 +166,7 @@ class AccountGatewayController extends BaseController $ids = Input::get('bulk_public_id'); $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); } @@ -236,14 +214,16 @@ class AccountGatewayController extends BaseController } } - foreach ($fields as $field => $details) { - if (!in_array($field, $optional)) { - if (strtolower($gateway->name) == 'beanstream') { - if (in_array($field, ['merchant_id', 'passCode'])) { - $rules[$gateway->id.'_'.$field] = 'required'; + if ($gatewayId != GATEWAY_WEPAY) { + foreach ($fields as $field => $details) { + if (!in_array($field, $optional)) { + if (strtolower($gateway->name) == 'beanstream') { + if (in_array($field, ['merchant_id', 'passCode'])) { + $rules[$gateway->id . '_' . $field] = 'required'; + } + } else { + $rules[$gateway->id . '_' . $field] = 'required'; } - } else { - $rules[$gateway->id.'_'.$field] = 'required'; } } } @@ -265,20 +245,32 @@ class AccountGatewayController extends BaseController } else { $accountGateway = AccountGateway::createNew(); $accountGateway->gateway_id = $gatewayId; + + if ($gatewayId == GATEWAY_WEPAY) { + if(!$this->setupWePay($accountGateway, $wepayResponse)) { + return $wepayResponse; + } + $oldConfig = $accountGateway->getConfig(); + } } $config = new stdClass(); - foreach ($fields as $field => $details) { - $value = trim(Input::get($gateway->id.'_'.$field)); - // if the new value is masked use the original value - if ($oldConfig && $value && $value === str_repeat('*', strlen($value))) { - $value = $oldConfig->$field; - } - if (!$value && ($field == 'testMode' || $field == 'developerMode')) { - // do nothing - } else { - $config->$field = $value; + + if ($gatewayId != GATEWAY_WEPAY) { + foreach ($fields as $field => $details) { + $value = trim(Input::get($gateway->id . '_' . $field)); + // if the new value is masked use the original value + if ($oldConfig && $value && $value === str_repeat('*', strlen($value))) { + $value = $oldConfig->$field; + } + if (!$value && ($field == 'testMode' || $field == 'developerMode')) { + // do nothing + } else { + $config->$field = $value; + } } + } elseif($oldConfig) { + $config = clone $oldConfig; } $publishableKey = Input::get('publishable_key'); @@ -340,16 +332,163 @@ class AccountGatewayController extends BaseController $account->save(); } - if ($accountGatewayPublicId) { - $message = trans('texts.updated_gateway'); + if(isset($wepayResponse)) { + return $wepayResponse; } else { - $message = trans('texts.created_gateway'); + if ($accountGatewayPublicId) { + $message = trans('texts.updated_gateway'); + } else { + $message = trans('texts.created_gateway'); + } + + Session::flash('message', $message); + return Redirect::to("gateways/{$accountGateway->public_id}/edit"); } - - Session::flash('message', $message); - - 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); + } } diff --git a/app/Http/Controllers/ExpenseApiController.php b/app/Http/Controllers/ExpenseApiController.php index 725067aa1f2c..6d190c1a48c9 100644 --- a/app/Http/Controllers/ExpenseApiController.php +++ b/app/Http/Controllers/ExpenseApiController.php @@ -1,7 +1,7 @@ id.'payment_ref', $invoice->id.'_'.uniqid()); + if ($paymentType != PAYMENT_TYPE_BRAINTREE_PAYPAL) { if ($paymentType == PAYMENT_TYPE_TOKEN) { $useToken = true; @@ -198,6 +200,10 @@ class PaymentController extends BaseController $data['braintreeClientToken'] = $this->paymentService->getBraintreeClientToken($account); } + if(!empty($data['braintreeClientToken']) || $accountGateway->getPublishableStripeKey()|| $accountGateway->gateway_id == GATEWAY_WEPAY) { + $data['tokenize'] = true; + } + } else { if ($deviceData = Input::get('details')) { Session::put($invitation->id . 'device_data', $deviceData); @@ -405,7 +411,7 @@ class PaymentController extends BaseController '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, [ @@ -433,7 +439,7 @@ class PaymentController extends BaseController $validator = Validator::make(Input::all(), $rules); if ($validator->fails()) { - return false; + return $validator; } if ($requireAddress && $accountGateway->update_address) { @@ -460,8 +466,6 @@ class PaymentController extends BaseController $accountGateway = $account->getGatewayByType($paymentType); $paymentMethod = null; - - if ($useToken) { if(!$sourceId) { 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) ->withErrors($validator) ->withInput(Request::except('cvv')); } + try { // For offsite payments send the client's details on file // If we're using a token then we don't need to send any other data @@ -492,21 +497,48 @@ class PaymentController extends BaseController $details = $this->paymentService->getPaymentDetails($invitation, $accountGateway, $data); // check if we're creating/using a billing token + $tokenBillingSupported = false; + $sourceReferenceParam = 'token'; if ($accountGateway->gateway_id == GATEWAY_STRIPE) { + $tokenBillingSupported = true; + $customerReferenceParam = 'customerReference'; + if ($paymentType == PAYMENT_TYPE_STRIPE_ACH && !Input::get('authorize_ach')) { Session::flash('error', trans('texts.ach_authorization_required')); 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) { - $details['customerReference'] = $customerReference; - unset($details['token']); - $details['cardReference'] = $sourceReference; + if ($customerReferenceParam) { + $details[$customerReferenceParam] = $customerReference; + } + $details[$sourceReferenceParam] = $sourceReference; + unset($details['card']); } elseif ($account->token_billing_type_id == TOKEN_BILLING_ALWAYS || Input::get('token_billing') || $paymentType == PAYMENT_TYPE_STRIPE_ACH) { $token = $this->paymentService->createToken($gateway, $details, $accountGateway, $client, $invitation->contact_id, $customerReference/* return parameter */, $paymentMethod/* return parameter */); if ($token) { - $details['token'] = $token; - $details['customerReference'] = $customerReference; + $details[$sourceReferenceParam] = $token; + if ($customerReferenceParam) { + $details[$customerReferenceParam] = $customerReference; + } if ($paymentType == PAYMENT_TYPE_STRIPE_ACH && empty(Input::get('plaidPublicToken')) ) { // The user needs to complete verification @@ -518,36 +550,6 @@ class PaymentController extends BaseController 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(); @@ -569,7 +571,7 @@ class PaymentController extends BaseController if (!$ref) { $this->error('No-Ref', $response->getMessage(), $accountGateway); - if ($onSite) { + if ($onSite && $paymentType != PAYMENT_TYPE_BRAINTREE_PAYPAL) { return Redirect::to('payment/'.$invitationKey) ->withInput(Request::except('cvv')); } else { @@ -597,7 +599,7 @@ class PaymentController extends BaseController $response->redirect(); } else { $this->error('Unknown', $response->getMessage(), $accountGateway); - if ($onSite) { + if ($onSite && $paymentType != PAYMENT_TYPE_BRAINTREE_PAYPAL) { return Redirect::to('payment/'.$invitationKey)->withInput(Request::except('cvv')); } else { return Redirect::to('view/'.$invitationKey); @@ -605,7 +607,7 @@ class PaymentController extends BaseController } } catch (\Exception $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')); } else { return Redirect::to('view/'.$invitationKey); @@ -760,7 +762,7 @@ class PaymentController extends BaseController 'message' => $data, ], 500); } elseif (!empty($data)) { - return $data; + return response()->json($data); } return response()->json([ @@ -791,6 +793,8 @@ class PaymentController extends BaseController switch($gatewayId) { case GATEWAY_STRIPE: return $this->handleStripeWebhook($accountGateway); + case GATEWAY_WEPAY: + return $this->handleWePayWebhook($accountGateway); default: return response()->json([ '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) { $eventId = Input::get('id'); $eventType= Input::get('type'); diff --git a/app/Http/Controllers/PublicClientController.php b/app/Http/Controllers/PublicClientController.php index 95f350dc04f0..79e236d70fc6 100644 --- a/app/Http/Controllers/PublicClientController.php +++ b/app/Http/Controllers/PublicClientController.php @@ -16,7 +16,7 @@ use Redirect; use App\Models\Gateway; use App\Models\Invitation; use App\Models\Document; -use App\ModelsPaymentMethod; +use App\Models\PaymentMethod; use App\Ninja\Repositories\InvoiceRepository; use App\Ninja\Repositories\PaymentRepository; use App\Ninja\Repositories\ActivityRepository; @@ -176,8 +176,10 @@ class PublicClientController extends BaseController $html = ''; if ($paymentMethod->payment_type_id == PAYMENT_TYPE_ACH) { - if($paymentMethod->bank_data) { + if ($paymentMethod->bank_data) { $html = '
' . htmlentities($paymentMethod->bank_data->name) . '
'; + } else { + $html = ''.trans('; } } elseif ($paymentMethod->payment_type_id == PAYMENT_TYPE_ID_PAYPAL) { $html = ''.trans('; @@ -860,7 +862,6 @@ class PublicClientController extends BaseController ]; if ($paymentType == PAYMENT_TYPE_STRIPE_ACH) { - $data['currencies'] = Cache::get('currencies'); } @@ -868,6 +869,10 @@ class PublicClientController extends BaseController $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); } @@ -883,10 +888,12 @@ class PublicClientController extends BaseController $account = $client->account; $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)) { - return Redirect::to('client/paymentmethods/add/' . $typeLink)->withInput(Request::except('cvv')); + if (($validator = PaymentController::processPaymentClientDetails($client, $accountGateway, $paymentType)) !== true) { + return Redirect::to('client/paymentmethods/add/' . $typeLink) + ->withErrors($validator) + ->withInput(Request::except('cvv')); } if ($sourceToken) { diff --git a/app/Http/routes.php b/app/Http/routes.php index 47b1a28b1c4e..ca6268b355cb 100644 --- a/app/Http/routes.php +++ b/app/Http/routes.php @@ -242,6 +242,8 @@ Route::group([ Route::post('/import_csv', 'ImportController@doImportCSV'); 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::post('account_gateways/bulk', 'AccountGatewayController@bulk'); @@ -563,6 +565,10 @@ if (!defined('CONTACT_EMAIL')) { define('GATEWAY_WEPAY', 60); 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_INVOICE', 2); define('EVENT_CREATE_QUOTE', 3); @@ -704,10 +710,10 @@ if (!defined('CONTACT_EMAIL')) { define('RESELLER_REVENUE_SHARE', 'A'); define('RESELLER_LIMITED_USERS', 'B'); - define('AUTO_BILL_OFF', 0); - define('AUTO_BILL_OPT_IN', 1); - define('AUTO_BILL_OPT_OUT', 2); - define('AUTO_BILL_ALWAYS', 3); + define('AUTO_BILL_OFF', 1); + define('AUTO_BILL_OPT_IN', 2); + define('AUTO_BILL_OPT_OUT', 3); + define('AUTO_BILL_ALWAYS', 4); // These must be lowercase 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 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 = [ 1 => ['card' => 'images/credit_cards/Test-Visa-Icon.png', 'text' => 'Visa'], 2 => ['card' => 'images/credit_cards/Test-MasterCard-Icon.png', 'text' => 'Master Card'], diff --git a/app/Libraries/Utils.php b/app/Libraries/Utils.php index 2ed2878a0878..e79d82643917 100644 --- a/app/Libraries/Utils.php +++ b/app/Libraries/Utils.php @@ -15,6 +15,7 @@ use Log; use DateTime; use stdClass; use Carbon; +use WePay; use App\Models\Currency; @@ -995,4 +996,21 @@ class Utils 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); + } + } } diff --git a/app/Listeners/ActivityListener.php b/app/Listeners/ActivityListener.php index 1df8edeb6848..14ba0da50ec9 100644 --- a/app/Listeners/ActivityListener.php +++ b/app/Listeners/ActivityListener.php @@ -307,8 +307,8 @@ class ActivityListener $this->activityRepo->create( $payment, ACTIVITY_TYPE_DELETE_PAYMENT, - $payment->amount, - $payment->amount * -1 + $payment->amount - $payment->refunded, + ($payment->amount - $payment->refunded) * -1 ); } @@ -343,8 +343,8 @@ class ActivityListener $this->activityRepo->create( $payment, ACTIVITY_TYPE_FAILED_PAYMENT, - $payment->amount, - $payment->amount * -1 + ($payment->amount - $payment->refunded), + ($payment->amount - $payment->refunded) * -1 ); } @@ -367,8 +367,8 @@ class ActivityListener $this->activityRepo->create( $payment, ACTIVITY_TYPE_RESTORE_PAYMENT, - $event->fromDeleted ? $payment->amount * -1 : 0, - $event->fromDeleted ? $payment->amount : 0 + $event->fromDeleted ? ($payment->amount - $payment->refunded) * -1 : 0, + $event->fromDeleted ? ($payment->amount - $payment->refunded) : 0 ); } } diff --git a/app/Models/Account.php b/app/Models/Account.php index 29a9abbd09be..c489d21b180b 100644 --- a/app/Models/Account.php +++ b/app/Models/Account.php @@ -1245,6 +1245,8 @@ class Account extends Eloquent return GATEWAY_STRIPE; } elseif ($this->isGatewayConfigured(GATEWAY_BRAINTREE)) { return GATEWAY_BRAINTREE; + } elseif ($this->isGatewayConfigured(GATEWAY_WEPAY)) { + return GATEWAY_WEPAY; } else { return false; } @@ -1410,6 +1412,37 @@ class Account extends Eloquent public function getFontFolders(){ 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) { diff --git a/app/Models/AccountGateway.php b/app/Models/AccountGateway.php index 6de95e8593cf..0c281856be66 100644 --- a/app/Models/AccountGateway.php +++ b/app/Models/AccountGateway.php @@ -77,7 +77,7 @@ class AccountGateway extends EntityModel return !empty($this->getConfigField('enableAch')); } - public function getPayPAlEnabled() + public function getPayPalEnabled() { return !empty($this->getConfigField('enablePayPal')); } diff --git a/app/Models/Payment.php b/app/Models/Payment.php index 9171c9d22c69..85a10acafbbe 100644 --- a/app/Models/Payment.php +++ b/app/Models/Payment.php @@ -167,7 +167,7 @@ class Payment extends EntityModel return ENTITY_PAYMENT; } - public function getBankData() + public function getBankDataAttribute() { if (!$this->routing_number) { return null; diff --git a/app/Models/PaymentMethod.php b/app/Models/PaymentMethod.php index 07c6d2e19e1f..a2f17b722ae6 100644 --- a/app/Models/PaymentMethod.php +++ b/app/Models/PaymentMethod.php @@ -63,7 +63,7 @@ class PaymentMethod extends EntityModel return $this->hasMany('App\Models\Payments'); } - public function getBankData() + public function getBankDataAttribute() { if (!$this->routing_number) { return null; diff --git a/app/Ninja/Repositories/AccountGatewayRepository.php b/app/Ninja/Repositories/AccountGatewayRepository.php index b61f854f2dc5..286c8416a21a 100644 --- a/app/Ninja/Repositories/AccountGatewayRepository.php +++ b/app/Ninja/Repositories/AccountGatewayRepository.php @@ -15,10 +15,14 @@ class AccountGatewayRepository extends BaseRepository public function find($accountId) { - return DB::table('account_gateways') + $query = DB::table('account_gateways') ->join('gateways', 'gateways.id', '=', 'account_gateways.gateway_id') - ->where('account_gateways.deleted_at', '=', null) - ->where('account_gateways.account_id', '=', $accountId) - ->select('account_gateways.public_id', 'gateways.name', 'account_gateways.deleted_at', 'account_gateways.gateway_id'); + ->where('account_gateways.account_id', '=', $accountId); + + 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'); } } diff --git a/app/Ninja/Repositories/InvoiceRepository.php b/app/Ninja/Repositories/InvoiceRepository.php index fd493ba16d93..979304ed01a0 100644 --- a/app/Ninja/Repositories/InvoiceRepository.php +++ b/app/Ninja/Repositories/InvoiceRepository.php @@ -324,7 +324,7 @@ class InvoiceRepository extends BaseRepository $invoice->start_date = Utils::toSqlDate($data['start_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->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 ) { $invoice->auto_bill = AUTO_BILL_OFF; diff --git a/app/Services/AccountGatewayService.php b/app/Services/AccountGatewayService.php index bd5635e0dd52..a992da5c82c9 100644 --- a/app/Services/AccountGatewayService.php +++ b/app/Services/AccountGatewayService.php @@ -2,6 +2,7 @@ use URL; use App\Models\Gateway; +use App\Models\AccountGateway; use App\Services\BaseService; use App\Ninja\Repositories\AccountGatewayRepository; @@ -41,7 +42,40 @@ class AccountGatewayService extends BaseService [ 'name', function ($model) { - return link_to("gateways/{$model->public_id}/edit", $model->name)->toHtml(); + if ($model->deleted_at) { + return $model->name; + } elseif ($model->gateway_id != GATEWAY_WEPAY) { + 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 .= ' ('.trans('texts.action_required').')'; + $url = $updateUri->uri; + $html = "{$linkText}"; + $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 [ [ + 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'), function ($model) { 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; } ] ]; diff --git a/app/Services/DatatableService.php b/app/Services/DatatableService.php index 3d9001a095b7..df4c9d93d55d 100644 --- a/app/Services/DatatableService.php +++ b/app/Services/DatatableService.php @@ -60,11 +60,7 @@ class DatatableService $str .= '
'; } - $str .= ''; + if (!empty($dropdown_contents)) { + $str .= ''; }); } diff --git a/app/Services/PaymentService.php b/app/Services/PaymentService.php index b97bbbd67de4..d1a0a29a7154 100644 --- a/app/Services/PaymentService.php +++ b/app/Services/PaymentService.php @@ -9,6 +9,7 @@ use Cache; use Omnipay; use Session; use CreditCard; +use WePay; use App\Models\Payment; use App\Models\PaymentMethod; use App\Models\Account; @@ -30,7 +31,8 @@ class PaymentService extends BaseService protected static $refundableGateways = array( GATEWAY_STRIPE, - GATEWAY_BRAINTREE + GATEWAY_BRAINTREE, + GATEWAY_WEPAY, ); public function __construct(PaymentRepository $paymentRepo, AccountRepository $accountRepo, DatatableService $datatableService) @@ -95,9 +97,9 @@ class PaymentService extends BaseService $data['ButtonSource'] = 'InvoiceNinja_SP'; }; - if ($input && $accountGateway->isGateway(GATEWAY_STRIPE)) { - if (!empty($input['stripeToken'])) { - $data['token'] = $input['stripeToken']; + if ($input) { + if (!empty($input['sourceToken'])) { + $data['token'] = $input['sourceToken']; unset($data['card']); } elseif (!empty($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; } @@ -125,7 +131,7 @@ class PaymentService extends BaseService $data['cvv'] = $input['cvv']; } - if (isset($input['country_id'])) { + if (isset($input['address1'])) { $country = Country::find($input['country_id']); $data = array_merge($data, [ @@ -216,7 +222,7 @@ class PaymentService extends BaseService 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) { return 'Unsupported gateway'; } @@ -232,15 +238,18 @@ class PaymentService extends BaseService 'amounts[]=' . intval($amount1) . '&amounts[]=' . intval($amount2) ); - if (!is_string($result)) { - $paymentMethod->status = PAYMENT_METHOD_STATUS_VERIFIED; - $paymentMethod->save(); - - if (!$paymentMethod->account_gateway_token->default_payment_method_id) { - $paymentMethod->account_gateway_token->default_payment_method_id = $paymentMethod->id; - $paymentMethod->account_gateway_token->save(); - } + if (is_string($result)) { + return $result; } + + $paymentMethod->status = PAYMENT_METHOD_STATUS_VERIFIED; + $paymentMethod->save(); + + if (!$paymentMethod->account_gateway_token->default_payment_method_id) { + $paymentMethod->account_gateway_token->default_payment_method_id = $paymentMethod->id; + $paymentMethod->account_gateway_token->save(); + } + return true; } @@ -266,6 +275,17 @@ class PaymentService extends BaseService if (!$response->isSuccessful()) { return $response->getMessage(); } + } elseif ($accountGateway->gateway_id == GATEWAY_WEPAY) { + try { + $wepay = Utils::setupWePay($accountGateway); + $wepay->request('/credit_card/delete', [ + 'client_id' => WEPAY_CLIENT_ID, + 'client_secret' => WEPAY_CLIENT_SECRET, + 'credit_card_id' => intval($paymentMethod->source_reference), + ]); + } catch (\WePayException $ex){ + return $ex->getMessage(); + } } $paymentMethod->delete(); @@ -291,16 +311,16 @@ class PaymentService extends BaseService { $customerReference = $client->getGatewayToken($accountGateway, $accountGatewayToken/* return paramenter */); - if ($customerReference) { + if ($customerReference && $customerReference != CUSTOMER_REFERENCE_LOCAL) { $details['customerReference'] = $customerReference; - if ($accountGateway->gateway->id == GATEWAY_STRIPE) { + if ($accountGateway->gateway_id == GATEWAY_STRIPE) { $customerResponse = $gateway->fetchCustomer(array('customerReference' => $customerReference))->send(); if (!$customerResponse->isSuccessful()) { $customerReference = null; // The customer might not exist anymore } - } elseif ($accountGateway->gateway->id == GATEWAY_BRAINTREE) { + } elseif ($accountGateway->gateway_id == GATEWAY_BRAINTREE) { $customer = $gateway->findCustomer($customerReference)->send()->getData(); 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'])) { $plaidResult = $this->getPlaidToken($accountGateway, $details['plaidPublicToken'], $details['plaidAccountId']); @@ -355,7 +375,7 @@ class PaymentService extends BaseService return; } } - } elseif ($accountGateway->gateway->id == GATEWAY_BRAINTREE) { + } elseif ($accountGateway->gateway_id == GATEWAY_BRAINTREE) { if (!$customerReference) { $tokenResponse = $gateway->createCustomer(array('customerData' => array()))->send(); if ($tokenResponse->isSuccessful()) { @@ -377,6 +397,38 @@ class PaymentService extends BaseService 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) { @@ -394,7 +446,7 @@ class PaymentService extends BaseService $accountGatewayToken->token = $customerReference; $accountGatewayToken->save(); - $paymentMethod = $this->createPaymentMethodFromGatewayResponse($tokenResponse, $accountGateway, $accountGatewayToken, $contactId); + $paymentMethod = $this->convertPaymentMethodFromGatewayResponse($tokenResponse, $accountGateway, $accountGatewayToken, $contactId); } else { $this->lastError = $tokenResponse->getMessage(); @@ -422,7 +474,7 @@ class PaymentService extends BaseService $paymentMethod->setRelation('currency', $currency); } } 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']); } else { return null; @@ -442,7 +494,7 @@ class PaymentService extends BaseService if ($source instanceof \Braintree\CreditCard) { $paymentMethod->payment_type_id = $this->parseCardType($source->cardType); $paymentMethod->last4 = $source->last4; - $paymentMethod->expiration = $source->expirationYear . '-' . $source->expirationMonth . '-00'; + $paymentMethod->expiration = $source->expirationYear . '-' . $source->expirationMonth . '-01'; } elseif ($source instanceof \Braintree\PayPalAccount) { $paymentMethod->email = $source->email; $paymentMethod->payment_type_id = PAYMENT_TYPE_ID_PAYPAL; @@ -456,8 +508,24 @@ class PaymentService extends BaseService return $paymentMethod; } + + 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 createPaymentMethodFromGatewayResponse($gatewayResponse, $accountGateway, $accountGatewayToken = null, $contactId = null) { + public function convertPaymentMethodFromGatewayResponse($gatewayResponse, $accountGateway, $accountGatewayToken = null, $contactId = null, $existingPaymentMethod = null) { if ($accountGateway->gateway_id == GATEWAY_STRIPE) { $data = $gatewayResponse->getData(); if (!empty($data['object']) && ($data['object'] == 'card' || $data['object'] == 'bank_account')) { @@ -470,7 +538,7 @@ class PaymentService extends BaseService } if ($source) { - $paymentMethod = $this->convertPaymentMethodFromStripe($source, $accountGatewayToken); + $paymentMethod = $this->convertPaymentMethodFromStripe($source, $accountGatewayToken, $existingPaymentMethod); } } elseif ($accountGateway->gateway_id == GATEWAY_BRAINTREE) { $data = $gatewayResponse->getData(); @@ -478,11 +546,16 @@ class PaymentService extends BaseService if (!empty($data->transaction)) { $transaction = $data->transaction; - $paymentMethod = $accountGatewayToken ? PaymentMethod::createNew($accountGatewayToken) : new PaymentMethod(); + if ($existingPaymentMethod) { + $paymentMethod = $existingPaymentMethod; + } else { + $paymentMethod = $accountGatewayToken ? PaymentMethod::createNew($accountGatewayToken) : new PaymentMethod(); + } + if ($transaction->paymentInstrumentType == 'credit_card') { $card = $transaction->creditCardDetails; $paymentMethod->last4 = $card->last4; - $paymentMethod->expiration = $card->expirationYear . '-' . $card->expirationMonth . '-00'; + $paymentMethod->expiration = $card->expirationYear . '-' . $card->expirationMonth . '-01'; $paymentMethod->payment_type_id = $this->parseCardType($card->cardType); } elseif ($transaction->paymentInstrumentType == 'paypal_account') { $paymentMethod->payment_type_id = PAYMENT_TYPE_ID_PAYPAL; @@ -490,9 +563,20 @@ class PaymentService extends BaseService } $paymentMethod->setRelation('payment_type', Cache::get('paymentTypes')->find($paymentMethod->payment_type_id)); } 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) { @@ -566,43 +650,49 @@ class PaymentService extends BaseService $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) { $data = $purchaseResponse->getData(); - $source = !empty($data['source'])?$data['source']:$data['card']; - $payment->payment_status_id = $data['status'] == 'succeeded' ? PAYMENT_STATUS_COMPLETED : PAYMENT_STATUS_PENDING; - - if ($source) { - $payment->last4 = $source['last4']; - - if ($source['object'] == 'bank_account') { - $payment->routing_number = $source['routing_number']; - $payment->payment_type_id = PAYMENT_TYPE_ACH; - } - else{ - $payment->expiration = $source['exp_year'] . '-' . $source['exp_month'] . '-00'; - $payment->payment_type_id = $this->parseCardType($source['brand']); - } - } - } elseif ($accountGateway->gateway_id == GATEWAY_BRAINTREE) { - $transaction = $purchaseResponse->getData()->transaction; - if ($transaction->paymentInstrumentType == 'credit_card') { - $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) { - $payment->payer_id = $payerId; } if ($paymentMethod) { - $payment->payment_method_id = $paymentMethod->id; + if ($paymentMethod->last4) { + $payment->last4 = $paymentMethod->last4; + } + + if ($paymentMethod->expiration) { + $payment->expiration = $paymentMethod->expiration; + } + + if ($paymentMethod->routing_number) { + $payment->routing_number = $paymentMethod->routing_number; + } + + if ($paymentMethod->payment_type_id) { + $payment->payment_type_id = $paymentMethod->payment_type_id; + } + + if ($paymentMethod->email) { + $payment->email = $paymentMethod->email; + } + + if ($payerId) { + $payment->payer_id = $payerId; + } + + if ($savePaymentMethod) { + $payment->payment_method_id = $paymentMethod->id; + } } $payment->save(); @@ -665,20 +755,29 @@ class PaymentService extends BaseService private function parseCardType($cardName) { $cardTypes = array( - 'Visa' => PAYMENT_TYPE_VISA, - 'American Express' => PAYMENT_TYPE_AMERICAN_EXPRESS, - 'MasterCard' => PAYMENT_TYPE_MASTERCARD, - 'Discover' => PAYMENT_TYPE_DISCOVER, - 'JCB' => PAYMENT_TYPE_JCB, - 'Diners Club' => PAYMENT_TYPE_DINERS, - 'Carte Blanche' => PAYMENT_TYPE_CARTE_BLANCHE, - 'China UnionPay' => PAYMENT_TYPE_UNIONPAY, - 'Laser' => PAYMENT_TYPE_LASER, - 'Maestro' => PAYMENT_TYPE_MAESTRO, - 'Solo' => PAYMENT_TYPE_SOLO, - 'Switch' => PAYMENT_TYPE_SWITCH + 'visa' => PAYMENT_TYPE_VISA, + 'americanexpress' => PAYMENT_TYPE_AMERICAN_EXPRESS, + 'amex' => PAYMENT_TYPE_AMERICAN_EXPRESS, + 'mastercard' => PAYMENT_TYPE_MASTERCARD, + 'discover' => PAYMENT_TYPE_DISCOVER, + 'jcb' => PAYMENT_TYPE_JCB, + 'dinersclub' => PAYMENT_TYPE_DINERS, + 'carteblanche' => PAYMENT_TYPE_CARTE_BLANCHE, + 'chinaunionpay' => PAYMENT_TYPE_UNIONPAY, + 'unionpay' => PAYMENT_TYPE_UNIONPAY, + 'laser' => PAYMENT_TYPE_LASER, + 'maestro' => PAYMENT_TYPE_MAESTRO, + 'solo' => PAYMENT_TYPE_SOLO, + 'switch' => PAYMENT_TYPE_SWITCH ); + $cardName = strtolower(str_replace(array(' ', '-', '_'), '', $cardName)); + + if (empty($cardTypes[$cardName]) && 1 == preg_match('/^('.implode('|', array_keys($cardTypes)).')/', $cardName, $matches)) { + // Some gateways return extra stuff after the card name + $cardName = $matches[1]; + } + if (!empty($cardTypes[$cardName])) { return $cardTypes[$cardName]; } else { @@ -725,6 +824,11 @@ class PaymentService extends BaseService $invitation = $invoice->invitations->first(); $token = $client->getGatewayToken($accountGateway/* return parameter */, $accountGatewayToken/* return parameter */); + + if (!$accountGatewayToken) { + return false; + } + $defaultPaymentMethod = $accountGatewayToken->default_payment_method; if (!$invitation || !$token || !$defaultPaymentMethod) { @@ -736,10 +840,9 @@ class PaymentService extends BaseService $details = $this->getPaymentDetails($invitation, $accountGateway); $details['customerReference'] = $token; - if ($accountGateway->gateway_id == GATEWAY_STRIPE) { - $details['cardReference'] = $defaultPaymentMethod->source_reference; - } elseif ($accountGateway->gateway_id == GATEWAY_BRAINTREE) { - $details['paymentMethodToken'] = $defaultPaymentMethod->source_reference; + $details['token'] = $defaultPaymentMethod->source_reference; + if ($accountGateway->gateway_id == GATEWAY_WEPAY) { + $details['transaction_id'] = 'autobill_'.$invoice->id; } // submit purchase/get response @@ -948,41 +1051,76 @@ class PaymentService extends BaseService if ($payment->payment_type_id != PAYMENT_TYPE_CREDIT) { $gateway = $this->createGateway($accountGateway); - $refund = $gateway->refund(array( - 'transactionReference' => $payment->transaction_reference, - 'amount' => $amount, - )); - $response = $refund->send(); - - if ($response->isSuccessful()) { - $payment->recordRefund($amount); + + if ($accountGateway->gateway_id != GATEWAY_WEPAY) { + $refund = $gateway->refund(array( + 'transactionReference' => $payment->transaction_reference, + 'amount' => $amount, + )); + $response = $refund->send(); + + if ($response->isSuccessful()) { + $payment->recordRefund($amount); + } else { + $data = $response->getData(); + + if ($data instanceof \Braintree\Result\Error) { + $error = $data->errors->deepAll()[0]; + if ($error && $error->code == 91506) { + if ($amount == $payment->amount) { + // This is an unsettled transaction; try to void it + $void = $gateway->void(array( + 'transactionReference' => $payment->transaction_reference, + )); + $response = $void->send(); + + if ($response->isSuccessful()) { + $payment->markVoided(); + } + } else { + $this->error('Unknown', 'Partial refund not allowed for unsettled transactions.', $accountGateway); + return false; + } + } + } + + if (!$response->isSuccessful()) { + $this->error('Unknown', $response->getMessage(), $accountGateway); + return false; + } + } } else { - $data = $response->getData(); + $wepay = \Utils::setupWePay($accountGateway); - if ($data instanceof \Braintree\Result\Error) { - $error = $data->errors->deepAll()[0]; - if ($error && $error->code == 91506) { + 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) { - // This is an unsettled transaction; try to void it - $void = $gateway->void(array( - 'transactionReference' => $payment->transaction_reference, - )); - $response = $void->send(); - - if ($response->isSuccessful()) { + 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); } } - if (!$response->isSuccessful()) { - $this->error('Unknown', $response->getMessage(), $accountGateway); - return false; - } } } else { $payment->recordRefund($amount); diff --git a/composer.json b/composer.json index 01c6a26f51cf..cff2d85bcbba 100644 --- a/composer.json +++ b/composer.json @@ -74,7 +74,8 @@ "league/flysystem-rackspace": "~1.0", "barracudanetworks/archivestream-php": "^1.0", "omnipay/braintree": "~2.0@dev", - "gatepay/FedACHdir": "dev-master@dev" + "gatepay/FedACHdir": "dev-master@dev", + "wepay/php-sdk": "^0.2" }, "require-dev": { "phpunit/phpunit": "~4.0", diff --git a/composer.lock b/composer.lock index e167cfb38e9c..d694f04f6960 100644 --- a/composer.lock +++ b/composer.lock @@ -4,8 +4,8 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "hash": "7139e4aedb2ac151079c50ee5c17f93c", - "content-hash": "a314d6c0a16785dd2395a7fd73cdc76d", + "hash": "b2471aea1af5ef67a1379ad95b5138f7", + "content-hash": "df30a311df0341933d4ff2c3aa5974a6", "packages": [ { "name": "agmscode/omnipay-agms", @@ -505,7 +505,7 @@ }, "dist": { "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", "shasum": "" }, @@ -2785,7 +2785,7 @@ }, "dist": { "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", "shasum": "" }, @@ -8057,6 +8057,53 @@ ], "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", "version": "3.0.0", @@ -10024,4 +10071,4 @@ "prefer-lowest": false, "platform": [], "platform-dev": [] -} \ No newline at end of file +} diff --git a/database/migrations/2016_04_23_182223_payments_changes.php b/database/migrations/2016_04_23_182223_payments_changes.php index 1c277d8da6b2..35a1b6134e9b 100644 --- a/database/migrations/2016_04_23_182223_payments_changes.php +++ b/database/migrations/2016_04_23_182223_payments_changes.php @@ -78,6 +78,11 @@ class PaymentsChanges extends Migration ->where('auto_bill', '=', 1) ->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) { @@ -113,11 +118,15 @@ class PaymentsChanges extends Migration $table->dropColumn('payment_method_id'); }); + \DB::table('invoices') + ->where('auto_bill', '=', AUTO_BILL_OFF) + ->update(array('auto_bill' => 0)); + \DB::table('invoices') ->where(function($query){ $query->where('auto_bill', '=', AUTO_BILL_ALWAYS); $query->orwhere(function($query){ - $query->where('auto_bill', '!=', AUTO_BILL_OFF); + $query->where('auto_bill', '!=', 0); $query->where('client_enable_auto_bill', '=', 1); }); }) diff --git a/public/built.js b/public/built.js index 14f82399435f..06790ce1e6ed 100644 --- a/public/built.js +++ b/public/built.js @@ -30933,7 +30933,7 @@ function truncate(string, length){ // Show/hide the 'Select' option in the datalists 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-status').hide(); }).mouseout(function() { diff --git a/public/js/script.js b/public/js/script.js index a76e6d37cd0e..a66c48768bb6 100644 --- a/public/js/script.js +++ b/public/js/script.js @@ -1042,7 +1042,7 @@ function truncate(string, length){ // Show/hide the 'Select' option in the datalists 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-status').hide(); }).mouseout(function() { diff --git a/resources/lang/en/texts.php b/resources/lang/en/texts.php index 920ff5dd7ca3..681640e2575b 100644 --- a/resources/lang/en/texts.php +++ b/resources/lang/en/texts.php @@ -1282,6 +1282,26 @@ $LANG = array( 'import_ofx' => 'Import OFX', 'ofx_file' => '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; diff --git a/resources/views/accounts/account_gateway.blade.php b/resources/views/accounts/account_gateway.blade.php index ade161f5b65f..155ef6579f69 100644 --- a/resources/views/accounts/account_gateway.blade.php +++ b/resources/views/accounts/account_gateway.blade.php @@ -5,15 +5,18 @@ @include('accounts.nav', ['selected' => ACCOUNT_PAYMENTS]) - {!! Former::open($url)->method($method)->rule()->addClass('warn-on-exit') !!} - {!! Former::populateField('token_billing_type_id', $account->token_billing_type_id) !!} - + @if(!$accountGateway && WEPAY_CLIENT_ID && !$account->getGatewayByType(PAYMENT_TYPE_CREDIT_CARD) && !$account->getGatewayByType(PAYMENT_TYPE_STRIPE)) + @include('accounts.partials.account_gateway_wepay') + @endif +

{!! trans($title) !!}

+ {!! Former::open($url)->method($method)->rule()->addClass('warn-on-exit') !!} + {!! Former::populateField('token_billing_type_id', $account->token_billing_type_id) !!} @if ($accountGateway) {!! Former::populateField('gateway_id', $accountGateway->gateway_id) !!} @@ -91,7 +94,7 @@ {!! Former::text('publishable_key') !!} @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') ->options($tokenBillingOptions) ->help(trans('texts.token_billing_help')) !!} @@ -101,7 +104,7 @@
- +
{!! trans('texts.stripe_webhook_help', [ 'link'=>''.trans('texts.stripe_webhook_help_link_text').'' ]) !!}
@@ -173,7 +176,6 @@
@endif
-
@@ -183,6 +185,7 @@ $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'))) !!} {!! Former::close() !!} +
diff --git a/resources/views/accounts/account_gateway_switch_wepay.blade.php b/resources/views/accounts/account_gateway_switch_wepay.blade.php new file mode 100644 index 000000000000..ae851881bed0 --- /dev/null +++ b/resources/views/accounts/account_gateway_switch_wepay.blade.php @@ -0,0 +1,7 @@ +@extends('header') + +@section('content') + @parent + @include('accounts.nav', ['selected' => ACCOUNT_PAYMENTS]) + @include('accounts.partials.account_gateway_wepay') +@stop \ No newline at end of file diff --git a/resources/views/accounts/partials/account_gateway_wepay.blade.php b/resources/views/accounts/partials/account_gateway_wepay.blade.php new file mode 100644 index 000000000000..803fc37ed103 --- /dev/null +++ b/resources/views/accounts/partials/account_gateway_wepay.blade.php @@ -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) !!} +
+
+

{!! trans('texts.online_payments') !!}

+
+
+ {!! 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'=>''.trans('texts.wepay_tos_link_text').''] + ))->value('true') !!} +
+ {!! Button::primary(trans('texts.sign_up_with_wepay')) + ->submit() + ->large() !!} + @if(isset($gateways)) +

+ {{ trans('texts.use_another_provider') }} + @endif +
+
+
+ + +{!! Former::close() !!} \ No newline at end of file diff --git a/resources/views/accounts/payments.blade.php b/resources/views/accounts/payments.blade.php index d2fcde895200..997190ec4f0a 100644 --- a/resources/views/accounts/payments.blade.php +++ b/resources/views/accounts/payments.blade.php @@ -4,6 +4,17 @@ @parent @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')) !!} +   + @endif + + @if ($showAdd) {!! Button::primary(trans('texts.add_gateway')) ->asLinkTo(URL::to('/gateways/create')) @@ -28,6 +39,14 @@ @stop \ No newline at end of file diff --git a/resources/views/invoices/edit.blade.php b/resources/views/invoices/edit.blade.php index cf399d041253..9deb5322452a 100644 --- a/resources/views/invoices/edit.blade.php +++ b/resources/views/invoices/edit.blade.php @@ -166,24 +166,24 @@ @if($account->getTokenGatewayId()) -
+
{!! 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([ - 0 => trans('texts.off'), - 1 => trans('texts.opt_in'), - 2 => trans('texts.opt_out'), - 3 => trans('texts.always'), + AUTO_BILL_OFF => trans('texts.off'), + AUTO_BILL_OPT_IN => trans('texts.opt_in'), + AUTO_BILL_OPT_OUT => trans('texts.opt_out'), + AUTO_BILL_ALWAYS => trans('texts.always'), ]) !!}
-
+
{{trans('texts.auto_bill')}}
{{trans('texts.opted_in')}} - ({{trans('texts.disable')}})
-
+
{{trans('texts.auto_bill')}}
{{trans('texts.opted_out')}} - ({{trans('texts.enable')}}) diff --git a/resources/views/payments/add_paymentmethod.blade.php b/resources/views/payments/add_paymentmethod.blade.php index 376230315322..6d4e9ff24185 100644 --- a/resources/views/payments/add_paymentmethod.blade.php +++ b/resources/views/payments/add_paymentmethod.blade.php @@ -3,221 +3,11 @@ @section('head') @parent @if (!empty($braintreeClientToken)) - - + @include('payments.tokenization_braintree') @elseif (isset($accountGateway) && $accountGateway->getPublishableStripeKey()) - - + @include('payments.tokenization_stripe') + @elseif (isset($accountGateway) && $accountGateway->gateway_id == GATEWAY_WEPAY) + @include('payments.tokenization_wepay') @else @endif - @stop @section('content') @@ -283,7 +72,6 @@ {{ Former::populateField('email', $contact->email) }} @if (!$client->country_id && $client->account->country_id) {{ Former::populateField('country_id', $client->account->country_id) }} - {{ Former::populateField('country', $client->account->country->iso_3166_2) }} @endif @if (!$client->currency_id && $client->account->currency_id) {{ Former::populateField('currency_id', $client->account->currency_id) }} @@ -351,7 +139,6 @@
{!! Former::text('first_name') ->placeholder(trans('texts.first_name')) - ->autocomplete('given-name') ->label('') !!}
@@ -451,33 +238,32 @@ ))->inline()->label(trans('texts.account_holder_type')); !!} {!! Former::text('account_holder_name') ->label(trans('texts.account_holder_name')) !!} - {!! Former::select('country') - ->label(trans('texts.country_id')) - ->fromQuery($countries, 'name', 'iso_3166_2') - ->addGroupClass('country-select') !!} - {!! Former::select('currency') - ->label(trans('texts.currency_id')) - ->fromQuery($currencies, 'name', 'code') - ->addGroupClass('currency-select') !!} - {!! Former::text('') - ->id('routing_number') - ->label(trans('texts.routing_number')) !!} -
-
-
-
-
- {!! Former::text('') - ->id('account_number') - ->label(trans('texts.account_number')) !!} - {!! Former::text('') - ->id('confirm_account_number') - ->label(trans('texts.confirm_account_number')) !!} - {!! Former::checkbox('authorize_ach') - ->text(trans('texts.ach_authorization', ['company'=>$account->getDisplayName()])) - ->label(' ') !!} + {!! Former::select('country_id') + ->label(trans('texts.country_id')) + ->fromQuery($countries, 'name', 'id') + ->addGroupClass('country-select') !!} + {!! Former::select('currency') + ->label(trans('texts.currency_id')) + ->fromQuery($currencies, 'name', 'code') + ->addGroupClass('currency-select') !!} + {!! Former::text('') + ->id('routing_number') + ->label(trans('texts.routing_number')) !!} +
+
+
+
+ {!! Former::text('') + ->id('account_number') + ->label(trans('texts.account_number')) !!} + {!! Former::text('') + ->id('confirm_account_number') + ->label(trans('texts.confirm_account_number')) !!}
+ {!! Former::checkbox('authorize_ach') + ->text(trans('texts.ach_authorization', ['company'=>$account->getDisplayName()])) + ->label(' ') !!}
{!! Button::success(strtoupper(trans('texts.add_account'))) ->submit() @@ -494,7 +280,7 @@

{{ trans('texts.paypal') }}

{{$paypalDetails->firstName}} {{$paypalDetails->lastName}}
{{$paypalDetails->email}}
- + @@ -524,7 +310,7 @@ @if (!empty($braintreeClientToken))
@else - {!! Former::text($accountGateway->getPublishableStripeKey() ? '' : 'card_number') + {!! Former::text(!empty($tokenize) ? '' : 'card_number') ->id('card_number') ->placeholder(trans('texts.card_number')) ->autocomplete('cc-number') @@ -535,7 +321,7 @@ @if (!empty($braintreeClientToken))
@else - {!! Former::text($accountGateway->getPublishableStripeKey() ? '' : 'cvv') + {!! Former::text(!empty($tokenize) ? '' : 'cvv') ->id('cvv') ->placeholder(trans('texts.cvv')) ->autocomplete('off') @@ -548,7 +334,7 @@ @if (!empty($braintreeClientToken))
@else - {!! Former::select($accountGateway->getPublishableStripeKey() ? '' : 'expiration_month') + {!! Former::select(!empty($tokenize) ? '' : 'expiration_month') ->id('expiration_month') ->autocomplete('cc-exp-month') ->placeholder(trans('texts.expiration_month')) @@ -571,7 +357,7 @@ @if (!empty($braintreeClientToken))
@else - {!! Former::select($accountGateway->getPublishableStripeKey() ? '' : 'expiration_year') + {!! Former::select(!empty($tokenize) ? '' : 'expiration_year') ->id('expiration_year') ->autocomplete('cc-exp-year') ->placeholder(trans('texts.expiration_year')) @@ -662,7 +448,7 @@ $('#routing_number, #country').on('change keypress keyup keydown paste', function(){setTimeout(function () { var routingNumber = $('#routing_number').val().replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, ''); - if (routingNumber.length != 9 || $("#country").val() != 'US' || routingNumberCache[routingNumber] === false) { + if (routingNumber.length != 9 || $("#country_id").val() != 840 || routingNumberCache[routingNumber] === false) { $('#bank_name').hide(); } else if (routingNumberCache[routingNumber]) { $('#bank_name').empty().append(routingNumberCache[routingNumber]).show(); diff --git a/resources/views/payments/paymentmethods_list.blade.php b/resources/views/payments/paymentmethods_list.blade.php index 776cc942869c..6c068b5f915a 100644 --- a/resources/views/payments/paymentmethods_list.blade.php +++ b/resources/views/payments/paymentmethods_list.blade.php @@ -60,7 +60,7 @@ @endif @if($paymentMethod->payment_type_id == PAYMENT_TYPE_ACH) @if($paymentMethod->bank_data) - {{ $paymentMethod->bank_data }} + {{ $paymentMethod->bank_data->name }} @endif @if($paymentMethod->status == PAYMENT_METHOD_STATUS_NEW) ({{trans('texts.complete_verification')}}) @@ -183,5 +183,6 @@ function setDefault(sourceId) { $('#default_id').val(sourceId); $('#defaultSourceForm').submit() + return false; } diff --git a/resources/views/payments/tokenization_braintree.blade.php b/resources/views/payments/tokenization_braintree.blade.php new file mode 100644 index 000000000000..3588ff9a67a0 --- /dev/null +++ b/resources/views/payments/tokenization_braintree.blade.php @@ -0,0 +1,67 @@ + + \ No newline at end of file diff --git a/resources/views/payments/tokenization_stripe.blade.php b/resources/views/payments/tokenization_stripe.blade.php new file mode 100644 index 000000000000..325393061866 --- /dev/null +++ b/resources/views/payments/tokenization_stripe.blade.php @@ -0,0 +1,154 @@ + + \ No newline at end of file diff --git a/resources/views/payments/tokenization_wepay.blade.php b/resources/views/payments/tokenization_wepay.blade.php new file mode 100644 index 000000000000..5d41cfc9fc69 --- /dev/null +++ b/resources/views/payments/tokenization_wepay.blade.php @@ -0,0 +1,62 @@ + + \ No newline at end of file