diff --git a/app/Http/Controllers/ClientPortalController.php b/app/Http/Controllers/ClientPortalController.php index 50058c8bc8..2bb550759b 100644 --- a/app/Http/Controllers/ClientPortalController.php +++ b/app/Http/Controllers/ClientPortalController.php @@ -189,8 +189,8 @@ class ClientPortalController extends BaseController $html = ''; if ($paymentMethod->payment_type_id == PAYMENT_TYPE_ACH) { - if ($paymentMethod->bank_data) { - $html = '
' . htmlentities($paymentMethod->bank_data->name) . '
'; + if ($paymentMethod->bank_name) { + $html = '
' . htmlentities($paymentMethod->bank_name) . '
'; } else { $html = ''.trans('; } @@ -304,6 +304,7 @@ class ClientPortalController extends BaseController $data = [ 'color' => $color, + 'contact' => $contact, 'account' => $account, 'client' => $client, 'clientFontUrl' => $account->getFontsUrl(), @@ -472,9 +473,16 @@ class ClientPortalController extends BaseController return $model->email; } } elseif ($model->last4) { - $bankData = PaymentMethod::lookupBankData($model->routing_number); - if (is_object($bankData)) { - return $bankData->name.'  •••' . $model->last4; + if($model->bank_name) { + $bankName = $model->bank_name; + } else { + $bankData = PaymentMethod::lookupBankData($model->routing_number); + if($bankData) { + $bankName = $bankData->name; + } + } + if (!empty($bankName)) { + return $bankName.'  •••' . $model->last4; } elseif($model->last4) { return '' . htmlentities($card_type) . '  •••' . $model->last4; } @@ -762,6 +770,7 @@ class ClientPortalController extends BaseController $data = array( 'account' => $account, + 'contact' => $contact, 'color' => $account->primary_color ? $account->primary_color : '#0b4d78', 'client' => $client, 'clientViewCSS' => $account->clientViewCSS(), @@ -835,7 +844,7 @@ class ClientPortalController extends BaseController $gateway = $accountGateway->gateway; if ($token && $paymentType == PAYMENT_TYPE_BRAINTREE_PAYPAL) { - $sourceReference = $this->paymentService->createToken($this->paymentService->createGateway($accountGateway), array('token'=>$token), $accountGateway, $client, $contact->id); + $sourceReference = $this->paymentService->createToken($paymentType, $this->paymentService->createGateway($accountGateway), array('token'=>$token), $accountGateway, $client, $contact->id); if(empty($sourceReference)) { $this->paymentMethodError('Token-No-Ref', $this->paymentService->lastError, $accountGateway); @@ -864,8 +873,12 @@ class ClientPortalController extends BaseController 'clientFontUrl' => $account->getFontsUrl(), 'showAddress' => $accountGateway->show_address, 'paymentTitle' => trans('texts.add_payment_method'), + 'sourceId' => $token, ]; + $details = json_decode(Input::get('details')); + $data['details'] = $details; + if ($paymentType == PAYMENT_TYPE_STRIPE_ACH) { $data['currencies'] = Cache::get('currencies'); } @@ -874,7 +887,7 @@ class ClientPortalController extends BaseController $data['braintreeClientToken'] = $this->paymentService->getBraintreeClientToken($account); } - if(!empty($data['braintreeClientToken']) || $accountGateway->getPublishableStripeKey()|| $accountGateway->gateway_id == GATEWAY_WEPAY) { + if(!empty($data['braintreeClientToken']) || $accountGateway->getPublishableStripeKey()|| ($accountGateway->gateway_id == GATEWAY_WEPAY && $paymentType != PAYMENT_TYPE_WEPAY_ACH)) { $data['tokenize'] = true; } @@ -897,7 +910,7 @@ class ClientPortalController extends BaseController $sourceToken = Input::get('sourceToken'); if (($validator = PaymentController::processPaymentClientDetails($client, $accountGateway, $paymentType)) !== true) { - return Redirect::to('client/paymentmethods/add/' . $typeLink) + return Redirect::to('client/paymentmethods/add/' . $typeLink.'/'.$sourceToken) ->withErrors($validator) ->withInput(Request::except('cvv')); } @@ -909,21 +922,26 @@ class ClientPortalController extends BaseController $details = array('plaidPublicToken' => Input::get('plaidPublicToken'), 'plaidAccountId' => Input::get('plaidAccountId')); } - if ($paymentType == PAYMENT_TYPE_STRIPE_ACH && !Input::get('authorize_ach')) { + if (($paymentType == PAYMENT_TYPE_STRIPE_ACH || $paymentType == PAYMENT_TYPE_WEPAY_ACH) && !Input::get('authorize_ach')) { Session::flash('error', trans('texts.ach_authorization_required')); - return Redirect::to('client/paymentmethods/add/' . $typeLink)->withInput(Request::except('cvv')); + return Redirect::to('client/paymentmethods/add/' . $typeLink.'/'.$sourceToken)->withInput(Request::except('cvv')); + } + + if ($paymentType == PAYMENT_TYPE_WEPAY_ACH && !Input::get('tos_agree')) { + Session::flash('error', trans('texts.wepay_payment_tos_agree_required')); + return Redirect::to('client/paymentmethods/add/' . $typeLink.'/'.$sourceToken)->withInput(Request::except('cvv')); } if (!empty($details)) { $gateway = $this->paymentService->createGateway($accountGateway); - $sourceReference = $this->paymentService->createToken($gateway, $details, $accountGateway, $client, $contact->id); + $sourceReference = $this->paymentService->createToken($paymentType, $gateway, $details, $accountGateway, $client, $contact->id); } else { - return Redirect::to('client/paymentmethods/add/' . $typeLink)->withInput(Request::except('cvv')); + return Redirect::to('client/paymentmethods/add/' . $typeLink.'/'.$sourceToken)->withInput(Request::except('cvv')); } if(empty($sourceReference)) { $this->paymentMethodError('Token-No-Ref', $this->paymentService->lastError, $accountGateway); - return Redirect::to('client/paymentmethods/add/' . $typeLink)->withInput(Request::except('cvv')); + return Redirect::to('client/paymentmethods/add/' . $typeLink.'/'.$sourceToken)->withInput(Request::except('cvv')); } else if ($paymentType == PAYMENT_TYPE_STRIPE_ACH && empty($usingPlaid) ) { // The user needs to complete verification Session::flash('message', trans('texts.bank_account_verification_next_steps')); diff --git a/app/Http/Controllers/PaymentController.php b/app/Http/Controllers/PaymentController.php index 07d20a1571..f62f24f513 100644 --- a/app/Http/Controllers/PaymentController.php +++ b/app/Http/Controllers/PaymentController.php @@ -136,7 +136,6 @@ class PaymentController extends BaseController public function show_payment($invitationKey, $paymentType = false, $sourceId = false) { - $invitation = Invitation::with('invoice.invoice_items', 'invoice.client.currency', 'invoice.client.account.account_gateways.gateway')->where('invitation_key', '=', $invitationKey)->firstOrFail(); $invoice = $invitation->invoice; $client = $invoice->client; @@ -154,6 +153,9 @@ class PaymentController extends BaseController Session::put($invitation->id.'payment_ref', $invoice->id.'_'.uniqid()); + $details = json_decode(Input::get('details')); + $data['details'] = $details; + if ($paymentType != PAYMENT_TYPE_BRAINTREE_PAYPAL) { if ($paymentType == PAYMENT_TYPE_TOKEN) { $useToken = true; @@ -205,16 +207,14 @@ class PaymentController extends BaseController } } else { - if ($deviceData = Input::get('details')) { + if ($deviceData = Input::get('device_data')) { Session::put($invitation->id . 'device_data', $deviceData); } Session::put($invitation->id . 'payment_type', PAYMENT_TYPE_BRAINTREE_PAYPAL); - $paypalDetails = json_decode(Input::get('details')); - if (!$sourceId || !$paypalDetails) { + if (!$sourceId || !$details) { return Redirect::to('view/'.$invitationKey); } - $data['paypalDetails'] = $paypalDetails; } $data += [ @@ -405,7 +405,7 @@ class PaymentController extends BaseController } public static function processPaymentClientDetails($client, $accountGateway, $paymentType, $onSite = true){ - $rules = $paymentType == PAYMENT_TYPE_STRIPE_ACH ? [] : [ + $rules = ($paymentType == PAYMENT_TYPE_STRIPE_ACH || $paymentType == PAYMENT_TYPE_WEPAY_ACH)? [] : [ 'first_name' => 'required', 'last_name' => 'required', ]; @@ -422,7 +422,7 @@ class PaymentController extends BaseController ); } - $requireAddress = $accountGateway->show_address && $paymentType != PAYMENT_TYPE_STRIPE_ACH && $paymentType != PAYMENT_TYPE_BRAINTREE_PAYPAL; + $requireAddress = $accountGateway->show_address && $paymentType != PAYMENT_TYPE_STRIPE_ACH && $paymentType != PAYMENT_TYPE_BRAINTREE_PAYPAL && $paymentType != PAYMENT_TYPE_WEPAY_ACH; if ($requireAddress) { $rules = array_merge($rules, [ @@ -473,6 +473,21 @@ class PaymentController extends BaseController $customerReference = $client->getGatewayToken($accountGateway, $accountGatewayToken/* return parameter*/); $paymentMethod = PaymentMethod::scope($sourceId, $account->id, $accountGatewayToken->id)->firstOrFail(); $sourceReference = $paymentMethod->source_reference; + + // What type of payment is this? + if ($paymentMethod->payment_type_id == PAYMENT_TYPE_ACH) { + if ($accountGateway->gateway_id == GATEWAY_STRIPE) { + $paymentType = PAYMENT_TYPE_STRIPE_ACH; + } elseif ($accountGateway->gateway_id == GATEWAY_WEPAY) { + $paymentType = PAYMENT_TYPE_WEPAY_ACH; + } + } elseif ($paymentMethod->payment_type_id == PAYMENT_TYPE_ID_PAYPAL && $accountGateway->gateway_id == GATEWAY_BRAINTREE) { + $paymentType = PAYMENT_TYPE_BRAINTREE_PAYPAL; + } elseif ($accountGateway->gateway_id == GATEWAY_STRIPE) { + $paymentType = PAYMENT_TYPE_STRIPE_CREDIT_CARD; + } else { + $paymentType = PAYMENT_TYPE_CREDIT_CARD; + } } } @@ -494,6 +509,7 @@ class PaymentController extends BaseController $gateway = $this->paymentService->createGateway($accountGateway); $details = $this->paymentService->getPaymentDetails($invitation, $accountGateway, $data); + $details['paymentType'] = $paymentType; // check if we're creating/using a billing token $tokenBillingSupported = false; diff --git a/app/Models/Account.php b/app/Models/Account.php index de1328862b..98237ce02e 100644 --- a/app/Models/Account.php +++ b/app/Models/Account.php @@ -385,6 +385,10 @@ class Account extends Eloquent $type = PAYMENT_TYPE_STRIPE; } + if ($type == PAYMENT_TYPE_WEPAY_ACH) { + return $this->getGatewayConfig(GATEWAY_WEPAY); + } + foreach ($this->account_gateways as $gateway) { if ($exceptFor && ($gateway->id == $exceptFor->id)) { continue; diff --git a/app/Ninja/Datatables/PaymentDatatable.php b/app/Ninja/Datatables/PaymentDatatable.php index 22006bf742..92473c7fd2 100644 --- a/app/Ninja/Datatables/PaymentDatatable.php +++ b/app/Ninja/Datatables/PaymentDatatable.php @@ -65,9 +65,16 @@ class PaymentDatatable extends EntityDatatable return $model->email; } } elseif ($model->last4) { - $bankData = PaymentMethod::lookupBankData($model->routing_number); - if (is_object($bankData)) { - return $bankData->name.'  •••' . $model->last4; + if($model->bank_name) { + $bankName = $model->bank_name; + } else { + $bankData = PaymentMethod::lookupBankData($model->routing_number); + if($bankData) { + $bankName = $bankData->name; + } + } + if (!empty($bankName)) { + return $bankName.'  •••' . $model->last4; } elseif($model->last4) { return '' . htmlentities($card_type) . '  •••' . $model->last4; } diff --git a/app/Ninja/Repositories/PaymentRepository.php b/app/Ninja/Repositories/PaymentRepository.php index ef6c880964..498f0d1d69 100644 --- a/app/Ninja/Repositories/PaymentRepository.php +++ b/app/Ninja/Repositories/PaymentRepository.php @@ -58,6 +58,7 @@ class PaymentRepository extends BaseRepository 'payments.last4', 'payments.email', 'payments.routing_number', + 'payments.bank_name', 'invoices.is_deleted as invoice_is_deleted', 'gateways.name as gateway_name', 'gateways.id as gateway_id', @@ -129,6 +130,7 @@ class PaymentRepository extends BaseRepository 'payments.last4', 'payments.email', 'payments.routing_number', + 'payments.bank_name', 'payments.payment_status_id', 'payment_statuses.name as payment_status_name' ); diff --git a/app/Services/PaymentService.php b/app/Services/PaymentService.php index 0f1ede8b3f..77f489049f 100644 --- a/app/Services/PaymentService.php +++ b/app/Services/PaymentService.php @@ -306,7 +306,7 @@ class PaymentService extends BaseService return true; } - public function createToken($gateway, $details, $accountGateway, $client, $contactId, &$customerReference = null, &$paymentMethod = null) + public function createToken($paymentType, $gateway, $details, $accountGateway, $client, $contactId, &$customerReference = null, &$paymentMethod = null) { $customerReference = $client->getGatewayToken($accountGateway, $accountGatewayToken/* return paramenter */); @@ -398,27 +398,36 @@ class PaymentService extends BaseService } } 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']), - )); + if ($paymentType == PAYMENT_TYPE_WEPAY_ACH) { + // Persist bank details + $tokenResponse = $wepay->request('/payment_bank/persist', array( + 'client_id' => WEPAY_CLIENT_ID, + 'client_secret' => WEPAY_CLIENT_SECRET, + 'payment_bank_id' => intval($details['token']), + )); + } else { + // Authorize credit card + $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' => $accountGateway->getWebhookUrl(), - )); - $tokenResponse = $wepay->request('credit_card', 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' => $accountGateway->getWebhookUrl(), + )); + $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']; @@ -516,12 +525,29 @@ class PaymentService extends BaseService $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)); + if ($source->payment_bank_id) { + $paymentMethod->payment_type_id = PAYMENT_TYPE_ACH; + $paymentMethod->last4 = $source->account_last_four; + $paymentMethod->bank_name = $source->bank_name; + $paymentMethod->source_reference = $source->payment_bank_id; - $paymentMethod->source_reference = $source->credit_card_id; + switch($source->state) { + case 'new': + case 'pending': + $paymentMethod->status = 'new'; + break; + case 'authorized': + $paymentMethod->status = 'verified'; + break; + } + } else { + $paymentMethod->last4 = $source->last_four; + $paymentMethod->payment_type_id = $this->parseCardType($source->credit_card_name); + $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; } @@ -570,10 +596,12 @@ class PaymentService extends BaseService } elseif ($accountGateway->gateway_id == GATEWAY_WEPAY) { if ($gatewayResponse instanceof \Omnipay\WePay\Message\CustomCheckoutResponse) { $wepay = \Utils::setupWePay($accountGateway); - $gatewayResponse = $wepay->request('credit_card', array( + $paymentMethodType = $gatewayResponse->getData()['payment_method']['type']; + + $gatewayResponse = $wepay->request($paymentMethodType, array( 'client_id' => WEPAY_CLIENT_ID, 'client_secret' => WEPAY_CLIENT_SECRET, - 'credit_card_id' => $gatewayResponse->getData()['payment_method']['credit_card']['id'], + $paymentMethodType.'_id' => $gatewayResponse->getData()['payment_method'][$paymentMethodType]['id'], )); } @@ -690,6 +718,10 @@ class PaymentService extends BaseService $payment->email = $paymentMethod->email; } + if ($paymentMethod->bank_name) { + $payment->bank_name = $paymentMethod->bank_name; + } + if ($payerId) { $payment->payer_id = $payerId; } @@ -876,6 +908,7 @@ class PaymentService extends BaseService $details['customerReference'] = $token; $details['token'] = $defaultPaymentMethod->source_reference; + $details['paymentType'] = $defaultPaymentMethod->payment_type_id; if ($accountGateway->gateway_id == GATEWAY_WEPAY) { $details['transaction_id'] = 'autobill_'.$invoice->id; } @@ -1117,6 +1150,13 @@ class PaymentService extends BaseService $details['applicationFee'] = $this->calculateApplicationFee($accountGateway, $details['amount']); $details['feePayer'] = WEPAY_FEE_PAYER; $details['callbackUri'] = $accountGateway->getWebhookUrl(); + if(isset($details['paymentType'])) { + if($details['paymentType'] == PAYMENT_TYPE_ACH || $details['paymentType'] == PAYMENT_TYPE_WEPAY_ACH) { + $details['paymentMethodType'] = 'payment_bank'; + } + + unset($details['paymentType']); + } } $response = $gateway->purchase($details)->send(); diff --git a/resources/lang/en/texts.php b/resources/lang/en/texts.php index 5218c16fe5..c0f4f97f4e 100644 --- a/resources/lang/en/texts.php +++ b/resources/lang/en/texts.php @@ -1257,7 +1257,7 @@ $LANG = array( 'plaid_linked_status' => 'Your bank account at :bank', 'add_payment_method' => 'Add Payment Method', 'account_holder_type' => 'Account Holder Type', - 'ach_authorization' => 'I authorize :company to electronically debit my account and, if necessary, electronically credit my account to correct erroneous debits.', + 'ach_authorization' => 'I authorize :company to use my bank account for future payments and, if necessary, electronically credit my account to correct erroneous debits. I understand that I may cancel this authorization at any time by removing the payment method or by contacting :email.', 'ach_authorization_required' => 'You must consent to ACH transactions.', 'off' => 'Off', 'opt_in' => 'Opt-in', @@ -1327,6 +1327,12 @@ $LANG = array( 'auto_bill_on_due_date' => 'Auto bill on due date instead of send date', 'auto_bill_ach_date_help' => 'ACH auto bill will always happen on the due date', 'warn_change_auto_bill' => 'Due to NACHA rules, changes to this invoice may prevent ACH auto bill.', + + 'bank_account' => 'Bank Account', + 'payment_processed_through_wepay' => 'ACH payments will be processed using WePay.', + 'wepay_payment_tos_agree' => 'I agree to the WePay :terms and :privacy_policy.', + 'privacy_policy' => 'Privacy Policy', + 'wepay_payment_tos_agree_required' => 'You must agree to the WePay Terms of Service and Privacy Policy.', ); return $LANG; diff --git a/resources/views/payments/add_paymentmethod.blade.php b/resources/views/payments/add_paymentmethod.blade.php index e17624af22..db734f2299 100644 --- a/resources/views/payments/add_paymentmethod.blade.php +++ b/resources/views/payments/add_paymentmethod.blade.php @@ -6,7 +6,7 @@ @include('payments.tokenization_braintree') @elseif (isset($accountGateway) && $accountGateway->getPublishableStripeKey()) @include('payments.tokenization_stripe') - @elseif (isset($accountGateway) && $accountGateway->gateway_id == GATEWAY_WEPAY) + @elseif (isset($accountGateway) && $accountGateway->gateway_id == GATEWAY_WEPAY && $paymentType != PAYMENT_TYPE_WEPAY_ACH) @include('payments.tokenization_wepay') @else