Store Stripe card summary locally

This commit is contained in:
Joshua Dwire 2016-05-10 18:46:32 -04:00
parent ad39f106db
commit 88ec714cd5
12 changed files with 627 additions and 325 deletions

View File

@ -20,6 +20,7 @@ use App\Models\PaymentType;
use App\Models\License;
use App\Models\Payment;
use App\Models\Affiliate;
use App\Models\PaymentMethod;
use App\Ninja\Repositories\PaymentRepository;
use App\Ninja\Repositories\InvoiceRepository;
use App\Ninja\Repositories\AccountRepository;
@ -406,6 +407,7 @@ class PaymentController extends BaseController
$account = $client->account;
$paymentType = Session::get($invitation->id . 'payment_type');
$accountGateway = $account->getGatewayByType($paymentType);
$paymentMethod = null;
$rules = [
'first_name' => 'required',
@ -436,6 +438,17 @@ class PaymentController extends BaseController
]);
}
if ($useToken) {
if(!$sourceId) {
Session::flash('error', trans('texts.no_payment_method_specified'));
return Redirect::to('payment/' . $invitationKey)->withInput(Request::except('cvv'));
} else {
$customerReference = $client->getGatewayToken($accountGateway, $accountGatewayToken/* return parameter*/);
$paymentMethod = PaymentMethod::scope($sourceId, $account->id, $accountGatewayToken->id)->firstOrFail();
$sourceReference = $paymentMethod->source_reference;
}
}
if ($onSite) {
$validator = Validator::make(Input::all(), $rules);
@ -476,11 +489,9 @@ class PaymentController extends BaseController
}
if ($useToken) {
$details['customerReference'] = $client->getGatewayToken();
$details['customerReference'] = $customerReference;
unset($details['token']);
if ($sourceId) {
$details['cardReference'] = $sourceId;
}
$details['cardReference'] = $sourceReference;
} 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 */);
if ($token) {
@ -509,13 +520,8 @@ class PaymentController extends BaseController
}
if ($useToken) {
$details['customerId'] = $customerId = $client->getGatewayToken();
if (!$sourceId) {
$customer = $gateway->findCustomer($customerId)->send();
$details['paymentMethodToken'] = $customer->getData()->paymentMethods[0]->token;
} else {
$details['paymentMethodToken'] = $sourceId;
}
$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 */);
@ -536,7 +542,6 @@ class PaymentController extends BaseController
$response = $gateway->purchase($details)->send();
if ($accountGateway->gateway_id == GATEWAY_EWAY) {
$ref = $response->getData()['AccessCode'];
} elseif ($accountGateway->gateway_id == GATEWAY_TWO_CHECKOUT) {
@ -563,7 +568,7 @@ class PaymentController extends BaseController
}
if ($response->isSuccessful()) {
$payment = $this->paymentService->createPayment($invitation, $accountGateway, $ref, null, $details, $response);
$payment = $this->paymentService->createPayment($invitation, $accountGateway, $ref, null, $details, $paymentMethod, $response);
Session::flash('message', trans('texts.applied_payment'));
if ($account->account_key == NINJA_ACCOUNT_KEY) {
@ -660,7 +665,7 @@ class PaymentController extends BaseController
if ($response->isCancelled()) {
// do nothing
} elseif ($response->isSuccessful()) {
$payment = $this->paymentService->createPayment($invitation, $accountGateway, $ref, $payerId, $details, $purchaseResponse);
$payment = $this->paymentService->createPayment($invitation, $accountGateway, $ref, $payerId, $details, null, $purchaseResponse);
Session::flash('message', trans('texts.applied_payment'));
} else {
$this->error('offsite', $response->getMessage(), $accountGateway);
@ -738,7 +743,7 @@ class PaymentController extends BaseController
], 400);
}
$data = static::getBankData($routingNumber);
$data = PaymentMethod::lookupBankData($routingNumber);
if (is_string($data)) {
return response()->json([
@ -753,75 +758,10 @@ class PaymentController extends BaseController
], 404);
}
public static function getBankData($routingNumber) {
$cached = Cache::get('bankData:'.$routingNumber);
if ($cached != null) {
return $cached == false ? null : $cached;
}
$dataPath = base_path('vendor/gatepay/FedACHdir/FedACHdir.txt');
if (!file_exists($dataPath) || !$size = filesize($dataPath)) {
return 'Invalid data file';
}
$lineSize = 157;
$numLines = $size/$lineSize;
if ($numLines % 1 != 0) {
// The number of lines should be an integer
return 'Invalid data file';
}
// Format: http://www.sco.ca.gov/Files-21C/Bank_Master_Interface_Information_Package.pdf
$file = fopen($dataPath, 'r');
// Binary search
$low = 0;
$high = $numLines - 1;
while ($low <= $high) {
$mid = floor(($low + $high) / 2);
fseek($file, $mid * $lineSize);
$thisNumber = fread($file, 9);
if ($thisNumber > $routingNumber) {
$high = $mid - 1;
} else if ($thisNumber < $routingNumber) {
$low = $mid + 1;
} else {
$data = array('routing_number' => $thisNumber);
fseek($file, 26, SEEK_CUR);
$data['name'] = trim(fread($file, 36));
$data['address'] = trim(fread($file, 36));
$data['city'] = trim(fread($file, 20));
$data['state'] = fread($file, 2);
$data['zip'] = fread($file, 5).'-'.fread($file, 4);
$data['phone'] = fread($file, 10);
break;
}
}
if (!empty($data)) {
Cache::put('bankData:'.$routingNumber, $data, 5);
return $data;
} else {
Cache::put('bankData:'.$routingNumber, false, 5);
return null;
}
}
public function handlePaymentWebhook($accountKey, $gatewayId)
{
$gatewayId = intval($gatewayId);
if ($gatewayId != GATEWAY_STRIPE) {
return response()->json([
'message' => 'Unsupported gateway',
], 404);
}
$account = Account::where('accounts.account_key', '=', $accountKey)->first();
if (!$account) {
@ -830,27 +770,48 @@ class PaymentController extends BaseController
], 404);
}
$accountGateway = $account->getGatewayConfig(intval($gatewayId));
if (!$accountGateway) {
return response()->json([
'message' => 'Unknown gateway',
], 404);
}
switch($gatewayId) {
case GATEWAY_STRIPE:
return $this->handleStripeWebhook($accountGateway);
default:
return response()->json([
'message' => 'Unsupported gateway',
], 404);
}
}
protected function handleStripeWebhook($accountGateway) {
$eventId = Input::get('id');
$eventType= Input::get('type');
$accountId = $accountGateway->account_id;
if (!$eventId) {
return response()->json([
'message' => 'Missing event id',
], 400);
return response()->json(['message' => 'Missing event id'], 400);
}
if (!$eventType) {
return response()->json([
'message' => 'Missing event type',
], 400);
return response()->json(['message' => 'Missing event type'], 400);
}
if (!in_array($eventType, array('charge.failed', 'charge.succeeded'))) {
$supportedEvents = array(
'charge.failed',
'charge.succeeded',
'customer.source.updated',
'customer.source.deleted',
);
if (!in_array($eventType, $supportedEvents)) {
return array('message' => 'Ignoring event');
}
$accountGateway = $account->getGatewayConfig(intval($gatewayId));
// Fetch the event directly from Stripe for security
$eventDetails = $this->paymentService->makeStripeCall($accountGateway, 'GET', 'events/'.$eventId);
@ -861,21 +822,19 @@ class PaymentController extends BaseController
}
if ($eventType != $eventDetails['type']) {
return response()->json([
'message' => 'Event type mismatch',
], 400);
return response()->json(['message' => 'Event type mismatch'], 400);
}
if (!$eventDetails['pending_webhooks']) {
return response()->json([
'message' => 'This is not a pending event',
], 400);
return response()->json(['message' => 'This is not a pending event'], 400);
}
if ($eventType == 'charge.failed' || $eventType == 'charge.succeeded') {
$charge = $eventDetails['data']['object'];
$transactionRef = $charge['id'];
$payment = Payment::where('transaction_reference', '=', $transactionRef)->first();
$payment = Payment::scope(false, $accountId)->where('transaction_reference', '=', $transactionRef)->first();
if (!$payment) {
return array('message' => 'Unknown payment');
@ -889,6 +848,22 @@ class PaymentController extends BaseController
} elseif ($eventType == 'charge.succeeded') {
$payment->markComplete();
}
} elseif($eventType == 'customer.source.updated' || $eventType == 'customer.source.deleted') {
$source = $eventDetails['data']['object'];
$sourceRef = $source['id'];
$paymentMethod = PaymentMethod::scope(false, $accountId)->where('source_reference', '=', $sourceRef)->first();
if (!$paymentMethod) {
return array('message' => 'Unknown payment method');
}
if ($eventType == 'customer.source.deleted') {
$paymentMethod->delete();
} elseif ($eventType == 'customer.source.updated') {
$this->paymentService->convertPaymentMethodFromStripe($source, null, $paymentMethod)->save();
}
}
return array('message' => 'Processed successfully');
}

View File

@ -16,6 +16,7 @@ use Redirect;
use App\Models\Gateway;
use App\Models\Invitation;
use App\Models\Document;
use App\ModelsPaymentMethod;
use App\Ninja\Repositories\InvoiceRepository;
use App\Ninja\Repositories\PaymentRepository;
use App\Ninja\Repositories\ActivityRepository;
@ -170,28 +171,30 @@ class PublicClientController extends BaseController
if ($paymentMethods) {
foreach ($paymentMethods as $paymentMethod) {
if ($paymentMethod['type']->id != PAYMENT_TYPE_ACH || $paymentMethod['status'] == 'verified') {
if ($paymentMethod->payment_type_id != PAYMENT_TYPE_ACH || $paymentMethod->status == PAYMENT_METHOD_STATUS_VERIFIED) {
$code = htmlentities(str_replace(' ', '', strtolower($paymentMethod->payment_type->name)));
if ($paymentMethod['type']->id == PAYMENT_TYPE_ACH) {
$html = '<div>'.htmlentities($paymentMethod['bank_name']).'</div>';
} elseif ($paymentMethod['type']->id == PAYMENT_TYPE_ID_PAYPAL) {
if ($paymentMethod->payment_type_id == PAYMENT_TYPE_ACH) {
if($paymentMethod->bank_data) {
$html = '<div>' . htmlentities($paymentMethod->bank_data->name) . '</div>';
}
} elseif ($paymentMethod->payment_type_id == PAYMENT_TYPE_ID_PAYPAL) {
$html = '<img height="22" src="'.URL::to('/images/credit_cards/paypal.png').'" alt="'.trans("texts.card_".$code).'">';
} else {
$code = htmlentities(str_replace(' ', '', strtolower($paymentMethod['type']->name)));
$html = '<img height="22" src="'.URL::to('/images/credit_cards/'.$code.'.png').'" alt="'.trans("texts.card_".$code).'">';
}
$url = URL::to("/payment/{$invitation->invitation_key}/token/".$paymentMethod['id']);
$url = URL::to("/payment/{$invitation->invitation_key}/token/".$paymentMethod->public_id);
if ($paymentMethod['type']->id == PAYMENT_TYPE_ID_PAYPAL) {
$html .= '&nbsp;&nbsp;<span>'.$paymentMethod['email'].'</span>';
if ($paymentMethod->payment_type_id == PAYMENT_TYPE_ID_PAYPAL) {
$html .= '&nbsp;&nbsp;<span>'.$paymentMethod->email.'</span>';
$url .= '#braintree_paypal';
} elseif ($paymentMethod['type']->id != PAYMENT_TYPE_ACH) {
$html .= '<div class="pull-right" style="text-align:right">'.trans('texts.card_expiration', array('expires' => Utils::fromSqlDate($paymentMethod['expiration'], false)->format('m/y'))).'<br>';
$html .= '&bull;&bull;&bull;'.$paymentMethod['last4'].'</div>';
} elseif ($paymentMethod->payment_type_id != PAYMENT_TYPE_ACH) {
$html .= '<div class="pull-right" style="text-align:right">'.trans('texts.card_expiration', array('expires' => Utils::fromSqlDate($paymentMethod->expiration, false)->format('m/y'))).'<br>';
$html .= '&bull;&bull;&bull;'.$paymentMethod->last4.'</div>';
} else {
$html .= '<div style="text-align:right">';
$html .= '&bull;&bull;&bull;'.$paymentMethod['last4'].'</div>';
$html .= '&bull;&bull;&bull;'.$paymentMethod->last4.'</div>';
}
$paymentTypes[] = [
@ -452,9 +455,9 @@ class PublicClientController extends BaseController
return $model->email;
}
} elseif ($model->last4) {
$bankData = PaymentController::getBankData($model->routing_number);
if (is_array($bankData)) {
return $bankData['name'].'&nbsp; &bull;&bull;&bull;' . $model->last4;
$bankData = PaymentMethod::lookupBankData($model->routing_number);
if (is_object($bankData)) {
return $bankData->name.'&nbsp; &bull;&bull;&bull;' . $model->last4;
} elseif($model->last4) {
return '<img height="22" src="' . URL::to('/images/credit_cards/ach.png') . '" alt="' . htmlentities($card_type) . '">&nbsp; &bull;&bull;&bull;' . $model->last4;
}
@ -770,7 +773,7 @@ class PublicClientController extends BaseController
public function verifyPaymentMethod()
{
$sourceId = Input::get('source_id');
$publicId = Input::get('source_id');
$amount1 = Input::get('verification1');
$amount2 = Input::get('verification2');
@ -779,7 +782,7 @@ class PublicClientController extends BaseController
}
$client = $invitation->invoice->client;
$result = $this->paymentService->verifyClientPaymentMethod($client, $sourceId, $amount1, $amount2);
$result = $this->paymentService->verifyClientPaymentMethod($client, $publicId, $amount1, $amount2);
if (is_string($result)) {
Session::flash('error', $result);
@ -790,14 +793,14 @@ class PublicClientController extends BaseController
return redirect()->to($client->account->enable_client_portal?'/client/dashboard':'/client/paymentmethods/');
}
public function removePaymentMethod($sourceId)
public function removePaymentMethod($publicId)
{
if (!$invitation = $this->getInvitation()) {
return $this->returnError();
}
$client = $invitation->invoice->client;
$result = $this->paymentService->removeClientPaymentMethod($client, $sourceId);
$result = $this->paymentService->removeClientPaymentMethod($client, $publicId);
if (is_string($result)) {
Session::flash('error', $result);
@ -824,9 +827,9 @@ class PublicClientController extends BaseController
$gateway = $accountGateway->gateway;
if ($token && $paymentType == PAYMENT_TYPE_BRAINTREE_PAYPAL) {
$sourceId = $this->paymentService->createToken($this->paymentService->createGateway($accountGateway), array('token'=>$token), $accountGateway, $client, $invitation->contact_id);
$sourceReference = $this->paymentService->createToken($this->paymentService->createGateway($accountGateway), array('token'=>$token), $accountGateway, $client, $invitation->contact_id);
if(empty($sourceId)) {
if(empty($sourceReference)) {
$this->paymentMethodError('Token-No-Ref', $this->paymentService->lastError, $accountGateway);
} else {
Session::flash('message', trans('texts.payment_method_added'));
@ -895,12 +898,12 @@ class PublicClientController extends BaseController
if (!empty($details)) {
$gateway = $this->paymentService->createGateway($accountGateway);
$sourceId = $this->paymentService->createToken($gateway, $details, $accountGateway, $client, $invitation->contact_id);
$sourceReference = $this->paymentService->createToken($gateway, $details, $accountGateway, $client, $invitation->contact_id);
} else {
return Redirect::to('client/paymentmethods/add/' . $typeLink)->withInput(Request::except('cvv'));
}
if(empty($sourceId)) {
if(empty($sourceReference)) {
$this->paymentMethodError('Token-No-Ref', $this->paymentService->lastError, $accountGateway);
return Redirect::to('client/paymentmethods/add/' . $typeLink)->withInput(Request::except('cvv'));
} else if ($paymentType == PAYMENT_TYPE_STRIPE_ACH && empty($usingPlaid) ) {

View File

@ -647,6 +647,10 @@ if (!defined('CONTACT_EMAIL')) {
define('PAYMENT_TYPE_SOLO', 22);
define('PAYMENT_TYPE_SWITCH', 23);
define('PAYMENT_METHOD_STATUS_NEW', 'new');
define('PAYMENT_METHOD_STATUS_VERIFICATION_FAILED', 'verification_failed');
define('PAYMENT_METHOD_STATUS_VERIFIED', 'verified');
define('PAYMENT_TYPE_PAYPAL', 'PAYMENT_TYPE_PAYPAL');
define('PAYMENT_TYPE_STRIPE', 'PAYMENT_TYPE_STRIPE');
define('PAYMENT_TYPE_STRIPE_CREDIT_CARD', 'PAYMENT_TYPE_STRIPE_CREDIT_CARD');

View File

@ -8,4 +8,18 @@ class AccountGatewayToken extends Eloquent
use SoftDeletes;
protected $dates = ['deleted_at'];
public $timestamps = true;
protected $casts = [
'uses_local_payment_methods' => 'boolean',
];
public function payment_methods()
{
return $this->hasMany('App\Models\PaymentMethod');
}
public function default_payment_method()
{
return $this->hasOne('App\Models\PaymentMethod', 'id', 'default_payment_method_id');
}
}

View File

@ -261,7 +261,7 @@ class Client extends EntityModel
}
public function getGatewayToken(&$accountGateway)
public function getGatewayToken(&$accountGateway = null, &$token = null)
{
$account = $this->account;
@ -272,7 +272,10 @@ class Client extends EntityModel
if (!count($account->account_gateways)) {
return false;
}
if (!$accountGateway){
$accountGateway = $account->getTokenGateway();
}
if (!$accountGateway) {
return false;

View File

@ -8,6 +8,7 @@ use App\Events\PaymentWasVoided;
use App\Events\PaymentCompleted;
use App\Events\PaymentVoided;
use App\Events\PaymentFailed;
use App\Models\PaymentMethod;
use Laracasts\Presenter\PresentableTrait;
class Payment extends EntityModel
@ -58,6 +59,11 @@ class Payment extends EntityModel
return $this->belongsTo('App\Models\PaymentType');
}
public function payment_method()
{
return $this->belongsTo('App\Models\PaymentMethod');
}
public function payment_status()
{
return $this->belongsTo('App\Models\PaymentStatus');
@ -160,6 +166,14 @@ class Payment extends EntityModel
{
return ENTITY_PAYMENT;
}
public function getBankData()
{
if (!$this->routing_number) {
return null;
}
return PaymentMethod::lookupBankData($this->routing_number);
}
}
Payment::creating(function ($payment) {

View File

@ -0,0 +1,157 @@
<?php namespace App\Models;
use Cache;
use Eloquent;
use Illuminate\Database\Eloquent\SoftDeletes;
class PaymentMethod extends EntityModel
{
use SoftDeletes;
protected $dates = ['deleted_at'];
public $timestamps = true;
protected $hidden = ['id'];
public static function createNew($accountGatewayToken = null)
{
$entity = new PaymentMethod();
$entity->account_id = $accountGatewayToken->account_id;
$entity->account_gateway_token_id = $accountGatewayToken->id;
$lastEntity = static::scope(false, $entity->account_id);
$lastEntity = $lastEntity->orderBy('public_id', 'DESC')
->first();
if ($lastEntity) {
$entity->public_id = $lastEntity->public_id + 1;
} else {
$entity->public_id = 1;
}
return $entity;
}
public function account()
{
return $this->belongsTo('App\Models\Account');
}
public function contact()
{
return $this->belongsTo('App\Models\Contact');
}
public function account_gateway_token()
{
return $this->belongsTo('App\Models\AccountGatewayToken');
}
public function payment_type()
{
return $this->belongsTo('App\Models\PaymentType');
}
public function currency()
{
return $this->belongsTo('App\Models\Currency');
}
public function payments()
{
return $this->hasMany('App\Models\Payments');
}
public function getBankData()
{
if (!$this->routing_number) {
return null;
}
return static::lookupBankData($this->routing_number);
}
public function scopeScope($query, $publicId = false, $accountId = false, $accountGatewayTokenId = false)
{
$query = parent::scopeScope($query, $publicId, $accountId);
if ($accountGatewayTokenId) {
$query->where($this->getTable() . '.account_gateway_token_id', '=', $accountGatewayTokenId);
}
return $query;
}
public static function lookupBankData($routingNumber) {
$cached = Cache::get('bankData:'.$routingNumber);
if ($cached != null) {
return $cached == false ? null : $cached;
}
$dataPath = base_path('vendor/gatepay/FedACHdir/FedACHdir.txt');
if (!file_exists($dataPath) || !$size = filesize($dataPath)) {
return 'Invalid data file';
}
$lineSize = 157;
$numLines = $size/$lineSize;
if ($numLines % 1 != 0) {
// The number of lines should be an integer
return 'Invalid data file';
}
// Format: http://www.sco.ca.gov/Files-21C/Bank_Master_Interface_Information_Package.pdf
$file = fopen($dataPath, 'r');
// Binary search
$low = 0;
$high = $numLines - 1;
while ($low <= $high) {
$mid = floor(($low + $high) / 2);
fseek($file, $mid * $lineSize);
$thisNumber = fread($file, 9);
if ($thisNumber > $routingNumber) {
$high = $mid - 1;
} else if ($thisNumber < $routingNumber) {
$low = $mid + 1;
} else {
$data = new \stdClass();
$data->routing_number = $thisNumber;
fseek($file, 26, SEEK_CUR);
$data->name = trim(fread($file, 36));
$data->address = trim(fread($file, 36));
$data->city = trim(fread($file, 20));
$data->state = fread($file, 2);
$data->zip = fread($file, 5).'-'.fread($file, 4);
$data->phone = fread($file, 10);
break;
}
}
if (!empty($data)) {
Cache::put('bankData:'.$routingNumber, $data, 5);
return $data;
} else {
Cache::put('bankData:'.$routingNumber, false, 5);
return null;
}
}
}
PaymentMethod::deleting(function($paymentMethod) {
$accountGatewayToken = $paymentMethod->account_gateway_token;
if ($accountGatewayToken->default_payment_method_id == $paymentMethod->id) {
$newDefault = $accountGatewayToken->payment_methods->first(function($i, $paymentMethdod) use ($accountGatewayToken){
return $paymentMethdod->id != $accountGatewayToken->default_payment_method_id;
});
$accountGatewayToken->default_payment_method_id = $newDefault ? $newDefault->id : null;
$accountGatewayToken->save();
}
});

View File

@ -10,6 +10,7 @@ use Omnipay;
use Session;
use CreditCard;
use App\Models\Payment;
use App\Models\PaymentMethod;
use App\Models\Account;
use App\Models\Country;
use App\Models\Client;
@ -47,7 +48,7 @@ class PaymentService extends BaseService
public function createGateway($accountGateway)
{
$gateway = Omnipay::create($accountGateway->gateway->provider);
$gateway->initialize((array) $accountGateway->getConfig());
$gateway->initialize((array)$accountGateway->getConfig());
if ($accountGateway->isGateway(GATEWAY_DWOLLA)) {
if ($gateway->getSandbox() && isset($_ENV['DWOLLA_SANDBOX_KEY']) && isset($_ENV['DWOLLA_SANSBOX_SECRET'])) {
@ -66,7 +67,7 @@ class PaymentService extends BaseService
{
$invoice = $invitation->invoice;
$account = $invoice->account;
$key = $invoice->account_id.'-'.$invoice->invoice_number;
$key = $invoice->account_id . '-' . $invoice->invoice_number;
$currencyCode = $invoice->client->currency ? $invoice->client->currency->code : ($invoice->account->currency ? $invoice->account->currency->code : 'USD');
if ($input) {
@ -94,7 +95,7 @@ class PaymentService extends BaseService
$data['ButtonSource'] = 'InvoiceNinja_SP';
};
if($input && $accountGateway->isGateway(GATEWAY_STRIPE)) {
if ($input && $accountGateway->isGateway(GATEWAY_STRIPE)) {
if (!empty($input['stripeToken'])) {
$data['token'] = $input['stripeToken'];
unset($details['card']);
@ -174,198 +175,134 @@ class PaymentService extends BaseService
];
}
public function getClientPaymentMethods($client) {
$token = $client->getGatewayToken($accountGateway);
public function getClientPaymentMethods($client)
{
$token = $client->getGatewayToken($accountGateway/* return parameter */, $accountGatewayToken/* return parameter */);
if (!$token) {
return null;
}
if (!$accountGatewayToken->uses_local_payment_methods && $accountGateway->gateway_id == GATEWAY_STRIPE) {
// Migrate Stripe data
$gateway = $this->createGateway($accountGateway);
$paymentMethods = array();
if ($accountGateway->gateway_id == GATEWAY_STRIPE) {
$response = $gateway->fetchCustomer(array('customerReference' => $token))->send();
if (!$response->isSuccessful()) {
return null;
}
$data = $response->getData();
$default_source = $data['default_source'];
$sources = isset($data['sources']) ? $data['sources']['data'] : $data['cards']['data'];
$paymentTypes = Cache::get('paymentTypes');
$currencies = Cache::get('currencies');
// Load
$accountGatewayToken->payment_methods();
foreach ($sources as $source) {
if ($source['object'] == 'bank_account') {
$paymentMethods[] = array(
'id' => $source['id'],
'default' => $source['id'] == $default_source,
'type' => $paymentTypes->find(PAYMENT_TYPE_ACH),
'currency' => $currencies->where('code', strtoupper($source['currency']))->first(),
'last4' => $source['last4'],
'routing_number' => $source['routing_number'],
'bank_name' => $source['bank_name'],
'status' => $source['status'],
);
} elseif ($source['object'] == 'card') {
$paymentMethods[] = array(
'id' => $source['id'],
'default' => $source['id'] == $default_source,
'type' => $paymentTypes->find($this->parseCardType($source['brand'])),
'last4' => $source['last4'],
'expiration' => $source['exp_year'] . '-' . $source['exp_month'] . '-00',
);
}
}
} elseif ($accountGateway->gateway_id == GATEWAY_BRAINTREE) {
$response = $gateway->findCustomer($token)->send();
$data = $response->getData();
if (!($data instanceof \Braintree\Customer)) {
return null;
$paymentMethod = $this->convertPaymentMethodFromStripe($source, $accountGatewayToken);
if ($paymentMethod) {
$paymentMethod->save();
}
$sources = $data->paymentMethods;
$paymentTypes = Cache::get('paymentTypes');
$currencies = Cache::get('currencies');
foreach ($sources as $source) {
if ($source instanceof \Braintree\CreditCard) {
$paymentMethods[] = array(
'id' => $source->token,
'default' => $source->isDefault(),
'type' => $paymentTypes->find($this->parseCardType($source->cardType)),
'last4' => $source->last4,
'expiration' => $source->expirationYear . '-' . $source->expirationMonth . '-00',
);
} elseif ($source instanceof \Braintree\PayPalAccount) {
$paymentMethods[] = array(
'id' => $source->token,
'default' => $source->isDefault(),
'type' => $paymentTypes->find(PAYMENT_TYPE_ID_PAYPAL),
'email' => $source->email,
);
}
if ($data['default_source'] == $source['id']) {
$accountGatewayToken->default_payment_method_id = $paymentMethod->id;
}
}
return $paymentMethods;
$accountGatewayToken->uses_local_payment_methods = true;
$accountGatewayToken->save();
}
public function verifyClientPaymentMethod($client, $sourceId, $amount1, $amount2) {
return $accountGatewayToken->payment_methods;
}
public function verifyClientPaymentMethod($client, $publicId, $amount1, $amount2)
{
$token = $client->getGatewayToken($accountGateway);
if ($accountGateway->gateway_id != GATEWAY_STRIPE) {
return 'Unsupported gateway';
}
$paymentMethod = PaymentMethod::scope($publicId, $client->account_id, $accountGatewayToken->id)->firstOrFail();
// Omnipay doesn't support verifying payment methods
// Also, it doesn't want to urlencode without putting numbers inside the brackets
return $this->makeStripeCall(
$result = $this->makeStripeCall(
$accountGateway,
'POST',
'customers/'.$token.'/sources/'.$sourceId.'/verify',
'amounts[]='.intval($amount1).'&amounts[]='.intval($amount2)
'customers/' . $token . '/sources/' . $paymentMethod->source_reference . '/verify',
'amounts[]=' . intval($amount1) . '&amounts[]=' . intval($amount2)
);
}
public function removeClientPaymentMethod($client, $sourceId) {
$token = $client->getGatewayToken($accountGateway/* return parameter */);
if (!$token) {
return null;
}
if (!is_string($result)) {
$paymentMethod->status = PAYMENT_METHOD_STATUS_VERIFIED;
$paymentMethod->save();
$gateway = $this->createGateway($accountGateway);
if ($accountGateway->gateway_id == GATEWAY_STRIPE) {
$response = $gateway->deleteCard(array('customerReference' => $token, 'cardReference'=>$sourceId))->send();
if (!$response->isSuccessful()) {
return $response->getMessage();
}
} elseif ($accountGateway->gateway_id == GATEWAY_BRAINTREE) {
// Make sure the source is owned by this client
$sources = $this->getClientPaymentMethods($client);
$ownsSource = false;
foreach ($sources as $source) {
if ($source['id'] == $sourceId) {
$ownsSource = true;
break;
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 (!$ownsSource) {
return 'Unknown source';
}
$response = $gateway->deletePaymentMethod(array('token'=>$sourceId))->send();
if (!$response->isSuccessful()) {
return $response->getMessage();
}
}
return true;
}
public function setClientDefaultPaymentMethod($client, $sourceId) {
$token = $client->getGatewayToken($accountGateway/* return parameter */);
if (!$token) {
return null;
}
$gateway = $this->createGateway($accountGateway);
if ($accountGateway->gateway_id == GATEWAY_STRIPE) {
return $this->makeStripeCall(
$accountGateway,
'POST',
'customers/'.$token,
'default_card='.$sourceId
);
} elseif ($accountGateway->gateway_id == GATEWAY_BRAINTREE) {
// Make sure the source is owned by this client
$sources = $this->getClientPaymentMethods($client);
$ownsSource = false;
foreach ($sources as $source) {
if ($source['id'] == $sourceId) {
$ownsSource = true;
break;
}
}
if (!$ownsSource) {
return 'Unknown source';
}
$response = $gateway->updatePaymentMethod(array(
'token' => $sourceId,
'makeDefault' => true,
))->send();
if (!$response->isSuccessful()) {
return $response->getMessage();
}
}
return true;
}
public function createToken($gateway, $details, $accountGateway, $client, $contactId, &$customerReference = null)
public function removeClientPaymentMethod($client, $publicId)
{
$customerReference = $client->getGatewayToken();
$token = $client->getGatewayToken($accountGateway/* return parameter */, $accountGatewayToken/* return parameter */);
if (!$token) {
return null;
}
$paymentMethod = PaymentMethod::scope($publicId, $client->account_id, $accountGatewayToken->id)->firstOrFail();
$gateway = $this->createGateway($accountGateway);
if ($accountGateway->gateway_id == GATEWAY_STRIPE) {
$response = $gateway->deleteCard(array('customerReference' => $token, 'cardReference' => $paymentMethod->source_reference))->send();
if (!$response->isSuccessful()) {
return $response->getMessage();
}
} elseif ($accountGateway->gateway_id == GATEWAY_BRAINTREE) {
$response = $gateway->deletePaymentMethod(array('token' => $paymentMethod->source_reference))->send();
if (!$response->isSuccessful()) {
return $response->getMessage();
}
}
$paymentMethod->delete();
return true;
}
public function setClientDefaultPaymentMethod($client, $publicId)
{
$token = $client->getGatewayToken($accountGateway/* return parameter */, $accountGatewayToken/* return parameter */);
if (!$token) {
return null;
}
$paymentMethod = PaymentMethod::scope($publicId, $client->account_id, $accountGatewayToken->id)->firstOrFail();
$paymentMethod->account_gateway_token->default_payment_method_id = $paymentMethod->id;
$paymentMethod->account_gateway_token->save();
return true;
}
public function createToken($gateway, $details, $accountGateway, $client, $contactId, &$customerReference = null, &$paymentMethod = null)
{
$customerReference = $client->getGatewayToken($accountGateway, $accountGatewayToken/* return paramenter */);
if ($customerReference) {
$details['customerReference'] = $customerReference;
if ($accountGateway->gateway->id == GATEWAY_STRIPE) {
$customerResponse = $gateway->fetchCustomer(array('customerReference'=>$customerReference))->send();
$customerResponse = $gateway->fetchCustomer(array('customerReference' => $customerReference))->send();
if (!$customerResponse->isSuccessful()){
if (!$customerResponse->isSuccessful()) {
$customerReference = null; // The customer might not exist anymore
}
} elseif ($accountGateway->gateway->id == GATEWAY_BRAINTREE) {
$customer = $gateway->findCustomer($customerReference)->send()->getData();
if (!($customer instanceof \Braintree\Customer)){
if (!($customer instanceof \Braintree\Customer)) {
$customerReference = null; // The customer might not exist anymore
}
}
@ -447,6 +384,9 @@ class PaymentService extends BaseService
$token->token = $customerReference;
$token->save();
$paymentMethod = $this->createPaymentMethodFromGatewayResponse($tokenResponse, $accountGateway, $accountGatewayToken, $contactId);
} else {
$this->lastError = $tokenResponse->getMessage();
}
@ -454,6 +394,104 @@ class PaymentService extends BaseService
return $sourceReference;
}
public function convertPaymentMethodFromStripe($source, $accountGatewayToken = null, $paymentMethod = null) {
// Creating a new one or updating an existing one
if (!$paymentMethod) {
$paymentMethod = $accountGatewayToken ? PaymentMethod::createNew($accountGatewayToken) : new PaymentMethod();
}
$paymentMethod->last4 = $source['last4'];
$paymentMethod->source_reference = $source['id'];
if ($source['object'] == 'bank_account') {
$paymentMethod->routing_number = $source['routing_number'];
$paymentMethod->payment_type_id = PAYMENT_TYPE_ACH;
$paymentMethod->status = $source['status'];
$currency = Cache::get('currencies')->where('code', strtoupper($source['currency']))->first();
if ($currency) {
$paymentMethod->currency_id = $currency->id;
$paymentMethod->setRelation('currency', $currency);
}
} elseif ($source['object'] == 'card') {
$paymentMethod->expiration = $source['exp_year'] . '-' . $source['exp_month'] . '-00';
$paymentMethod->payment_type_id = $this->parseCardType($source['brand']);
} else {
return null;
}
$paymentMethod->setRelation('payment_type', Cache::get('paymentTypes')->find($paymentMethod->payment_type_id));
return $paymentMethod;
}
public function convertPaymentMethodFromBraintree($source, $accountGatewayToken = null, $paymentMethod = null) {
// Creating a new one or updating an existing one
if (!$paymentMethod) {
$paymentMethod = $accountGatewayToken ? PaymentMethod::createNew($accountGatewayToken) : new PaymentMethod();
}
if ($source instanceof \Braintree\CreditCard) {
$paymentMethod->payment_type_id = $this->parseCardType($source->cardType);
$paymentMethod->last4 = $source->last4;
$paymentMethod->expiration = $source->expirationYear . '-' . $source->expirationMonth . '-00';
} elseif ($source instanceof \Braintree\PayPalAccount) {
$paymentMethod->email = $source->email;
$paymentMethod->payment_type_id = PAYMENT_TYPE_ID_PAYPAL;
} else {
return null;
}
$paymentMethod->setRelation('payment_type', Cache::get('paymentTypes')->find($paymentMethod->payment_type_id));
$paymentMethod->source_reference = $source->token;
return $paymentMethod;
}
public function createPaymentMethodFromGatewayResponse($gatewayResponse, $accountGateway, $accountGatewayToken = null, $contactId = null) {
if ($accountGateway->gateway_id == GATEWAY_STRIPE) {
$data = $gatewayResponse->getData();
if(!empty($data['object']) && ($data['object'] == 'card' || $data['object'] == 'bank_account')) {
$source = $data;
} else {
$source = !empty($data['source']) ? $data['source'] : $data['card'];
}
if ($source) {
$paymentMethod = $this->convertPaymentMethodFromStripe($source, $accountGatewayToken);
}
} elseif ($accountGateway->gateway_id == GATEWAY_BRAINTREE) {
$paymentMethod = $accountGatewayToken ? PaymentMethod::createNew($accountGatewayToken) : new PaymentMethod();
$transaction = $sourceResponse->getData()->transaction;
if ($transaction->paymentInstrumentType == 'credit_card') {
$card = $transaction->creditCardDetails;
$paymentMethod->last4 = $card->last4;
$paymentMethod->expiration = $card->expirationYear . '-' . $card->expirationMonth . '-00';
$paymentMethod->payment_type_id = $this->parseCardType($card->cardType);
} elseif ($transaction->paymentInstrumentType == 'paypal_account') {
$paymentMethod->payment_type_id = PAYMENT_TYPE_ID_PAYPAL;
$paymentMethod->email = $transaction->paypalDetails->payerEmail;
}
$paymentMethod->setRelation('payment_type', Cache::get('paymentTypes')->find($paymentMethod->payment_type_id));
}
if (!empty($paymentMethod) && $accountGatewayToken && $contactId) {
$paymentMethod->account_gateway_token_id = $accountGatewayToken->id;
$paymentMethod->account_id = $accountGatewayToken->account_id;
$paymentMethod->contact_id = $contactId;
$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 $paymentMethod;
}
public function getCheckoutComToken($invitation)
{
$token = false;
@ -490,7 +528,7 @@ class PaymentService extends BaseService
return $token;
}
public function createPayment($invitation, $accountGateway, $ref, $payerId = null, $paymentDetails = null, $purchaseResponse = null)
public function createPayment($invitation, $accountGateway, $ref, $payerId = null, $paymentDetails = null, $paymentMethod = null, $purchaseResponse = null)
{
$invoice = $invitation->invoice;
@ -551,6 +589,10 @@ class PaymentService extends BaseService
$payment->payer_id = $payerId;
}
if ($paymentMethod) {
$payment->payment_method_id = $paymentMethod->id;
}
$payment->save();
// enable pro plan for hosted users
@ -665,28 +707,28 @@ class PaymentService extends BaseService
public function autoBillInvoice($invoice)
{
$client = $invoice->client;
$account = $invoice->account;
$invitation = $invoice->invitations->first();
$accountGateway = $account->getTokenGateway();
$token = $client->getGatewayToken();
if (!$invitation || !$accountGateway || !$token) {
// Make sure we've migrated in data from Stripe
$this->getClientPaymentMethods($client);
$invitation = $invoice->invitations->first();
$token = $client->getGatewayToken($accountGateway/* return parameter */, $accountGatewayToken/* return parameter */);
$defaultPaymentMethod = $accountGatewayToken->default_payment_method;
if (!$invitation || !$token || !$defaultPaymentMethod) {
return false;
}
// setup the gateway/payment info
$gateway = $this->createGateway($accountGateway);
$details = $this->getPaymentDetails($invitation, $accountGateway);
$details['customerReference'] = $token;
if ($accountGateway->gateway_id == GATEWAY_STRIPE) {
$details['customerReference'] = $token;
$details['cardReference'] = $defaultPaymentMethod->sourceReference;
} elseif ($accountGateway->gateway_id == GATEWAY_BRAINTREE) {
$details['customerId'] = $token;
$customer = $gateway->findCustomer($token)->send()->getData();
$defaultPaymentMethod = $customer->defaultPaymentMethod();
$details['paymentMethodToken'] = $defaultPaymentMethod->token;
$details['paymentMethodToken'] = $defaultPaymentMethod->sourceReference;
}
// submit purchase/get response
@ -694,7 +736,7 @@ class PaymentService extends BaseService
if ($response->isSuccessful()) {
$ref = $response->getTransactionReference();
return $this->createPayment($invitation, $accountGateway, $ref, null, $details, $response);
return $this->createPayment($invitation, $accountGateway, $ref, null, $details, $defaultPaymentMethod, $response);
} else {
return false;
}
@ -761,9 +803,9 @@ class PaymentService extends BaseService
return $model->email;
}
} elseif ($model->last4) {
$bankData = PaymentController::getBankData($model->routing_number);
if (is_array($bankData)) {
return $bankData['name'].'&nbsp; &bull;&bull;&bull;' . $model->last4;
$bankData = PaymentMethod::lookupBankData($model->routing_number);
if (is_object($bankData)) {
return $bankData->name.'&nbsp; &bull;&bull;&bull;' . $model->last4;
} elseif($model->last4) {
return '<img height="22" src="' . URL::to('/images/credit_cards/ach.png') . '" alt="' . htmlentities($card_type) . '">&nbsp; &bull;&bull;&bull;' . $model->last4;
}

View File

@ -0,0 +1,83 @@
<?php
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class WePayIntegration extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('payment_methods', function($table)
{
$table->increments('id');
$table->unsignedInteger('account_id');
$table->unsignedInteger('contact_id')->nullable();
$table->unsignedInteger('account_gateway_token_id');
$table->unsignedInteger('payment_type_id');
$table->string('source_reference');
$table->unsignedInteger('routing_number')->nullable();
$table->smallInteger('last4')->unsigned()->nullable();
$table->date('expiration')->nullable();
$table->string('email')->nullable();
$table->unsignedInteger('currency_id')->nullable();
$table->string('status')->nullable();
$table->timestamps();
$table->softDeletes();
$table->foreign('account_id')->references('id')->on('accounts')->onDelete('cascade');
$table->foreign('contact_id')->references('id')->on('contacts')->onDelete('cascade');
$table->foreign('account_gateway_token_id')->references('id')->on('account_gateway_tokens');
$table->foreign('payment_type_id')->references('id')->on('payment_types');
$table->foreign('currency_id')->references('id')->on('currencies');
$table->unsignedInteger('public_id')->index();
$table->unique( array('account_id','public_id') );
});
Schema::table('payments', function($table)
{
$table->unsignedInteger('payment_method_id')->nullable();
$table->foreign('payment_method_id')->references('id')->on('payment_methods');
});
Schema::table('account_gateway_tokens', function($table)
{
$table->unsignedInteger('default_payment_method_id')->nullable();
$table->foreign('default_payment_method_id')->references('id')->on('payment_methods');
$table->boolean('uses_local_payment_methods')->defalut(true);
});
\DB::table('account_gateway_tokens')->update(array('uses_local_payment_methods' => false));
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('payments', function($table)
{
$table->dropForeign('payments_payment_method_id_foreign');
$table->dropColumn('payment_method_id');
});
Schema::table('account_gateway_tokens', function($table)
{
$table->dropForeign('account_gateway_tokens_default_payment_method_id_foreign');
$table->dropColumn('default_payment_method_id');
$table->dropColumn('uses_local_payment_methods');
});
Schema::dropIfExists('payment_methods');
}
}

View File

@ -1259,7 +1259,7 @@ $LANG = array(
'payment_method_set_as_default' => 'Set Autobill payment method.',
'activity_41' => ':payment_amount payment (:payment) failed',
'webhook_url' => 'Webhook URL',
'stripe_webhook_help' => 'You must :link for ACH payment status to be updated.',
'stripe_webhook_help' => 'You must :link.',
'stripe_webhook_help_link_text' => 'add this URL as an endpoint at Stripe',
'payment_method_error' => 'There was an error adding your payment methd. Please try again later.',
'notification_invoice_payment_failed_subject' => 'Payment failed for Invoice :invoice',
@ -1286,7 +1286,10 @@ $LANG = array(
'braintree_paypal_help' => 'You must also :link.',
'braintree_paypal_help_link_text' => 'link PayPal to your BrainTree account',
'token_billing_braintree_paypal' => 'Save payment details',
'add_paypal_account' => 'Add PayPal Account'
'add_paypal_account' => 'Add PayPal Account',
'no_payment_method_specified' => 'No payment method specified',
);
return $LANG;

View File

@ -97,6 +97,18 @@
->help(trans('texts.token_billing_help')) !!}
@endif
@if ($gateway->id == GATEWAY_STRIPE)
<div class="form-group">
<label class="control-label col-lg-4 col-sm-4">{{ trans('texts.webhook_url') }}</label>
<div class="col-lg-8 col-sm-8 help-block">
<input type="text" class="form-control" onfocus="$(this).select()" readonly value="{{ URL::to('/paymenthook/'.$account->account_key.'/'.GATEWAY_STRIPE) }}">
<div class="help-block"><strong>{!! trans('texts.stripe_webhook_help', [
'link'=>'<a href="https://dashboard.stripe.com/account/webhooks" target="_blank">'.trans('texts.stripe_webhook_help_link_text').'</a>'
]) !!}</strong></div>
</div>
</div>
@endif
@if ($gateway->id == GATEWAY_BRAINTREE)
@if ($account->getGatewayByType(PAYMENT_TYPE_PAYPAL))
{!! Former::checkbox('enable_paypal')
@ -148,15 +160,6 @@
->text(trans('texts.enable_ach'))
->help(trans('texts.stripe_ach_help')) !!}
<div class="stripe-ach-options">
<div class="form-group">
<label class="control-label col-lg-4 col-sm-4">{{ trans('texts.webhook_url') }}</label>
<div class="col-lg-8 col-sm-8 help-block">
<input type="text" class="form-control" onfocus="$(this).select()" readonly value="{{ URL::to('/paymenthook/'.$account->account_key.'/'.GATEWAY_STRIPE) }}">
<div class="help-block"><strong>{!! trans('texts.stripe_webhook_help', [
'link'=>'<a href="https://dashboard.stripe.com/account/webhooks" target="_blank">'.trans('texts.stripe_webhook_help_link_text').'</a>'
]) !!}</strong></div>
</div>
</div>
<div class="form-group">
<div class="col-sm-8 col-sm-offset-4">
<h4>{{trans('texts.plaid')}}</h4>

View File

@ -53,30 +53,31 @@
@foreach ($paymentMethods as $paymentMethod)
<div class="payment_method">
<span class="payment_method_img_container">
<img height="22" src="{{URL::to('/images/credit_cards/'.str_replace(' ', '', strtolower($paymentMethod['type']->name).'.png'))}}" alt="{{trans("texts.card_" . str_replace(' ', '', strtolower($paymentMethod['type']->name)))}}">
<img height="22" src="{{URL::to('/images/credit_cards/'.str_replace(' ', '', strtolower($paymentMethod->payment_type->name).'.png'))}}" alt="{{trans("texts.card_" . str_replace(' ', '', strtolower($paymentMethod->payment_type->name)))}}">
</span>
@if(!empty($paymentMethod['last4']))
<span class="payment_method_number">&bull;&bull;&bull;&bull;&bull;{{$paymentMethod['last4']}}</span>
@if(!empty($paymentMethod->last4))
<span class="payment_method_number">&bull;&bull;&bull;&bull;&bull;{{$paymentMethod->last4}}</span>
@endif
@if($paymentMethod['type']->id == PAYMENT_TYPE_ACH)
{{ $paymentMethod['bank_name'] }}
@if($paymentMethod['status'] == 'new')
<a href="javasript::void" onclick="completeVerification('{{$paymentMethod['id']}}','{{$paymentMethod['currency']->symbol}}')">({{trans('texts.complete_verification')}})</a>
@elseif($paymentMethod['status'] == 'verification_failed')
@if($paymentMethod->payment_type_id == PAYMENT_TYPE_ACH)
@if($paymentMethod->bank())
{{ $paymentMethod->bank()->name }}
@endif
@if($paymentMethod->status == PAYMENT_METHOD_STATUS_NEW)
<a href="javasript::void" onclick="completeVerification('{{$paymentMethod->public_id}}','{{$paymentMethod->currency->symbol}}')">({{trans('texts.complete_verification')}})</a>
@elseif($paymentMethod->status == PAYMENT_METHOD_STATUS_VERIFICATION_FAILED)
({{trans('texts.verification_failed')}})
@endif
@elseif($paymentMethod['type']->id == PAYMENT_TYPE_ID_PAYPAL)
{{ $paymentMethod['email'] }}
@elseif($paymentMethod->type_id == PAYMENT_TYPE_ID_PAYPAL)
{{ $paymentMethod->email }}
@else
{!! trans('texts.card_expiration', array('expires'=>Utils::fromSqlDate($paymentMethod['expiration'], false)->format('m/y'))) !!}
{!! trans('texts.card_expiration', array('expires'=>Utils::fromSqlDate($paymentMethod->expiration, false)->format('m/y'))) !!}
@endif
@if($paymentMethod['default'])
@if($paymentMethod->id == $paymentMethod->account_gateway_token->default_payment_method_id)
({{trans('texts.used_for_auto_bill')}})
@elseif($paymentMethod['type']->id != PAYMENT_TYPE_ACH || $paymentMethod['status'] == 'verified')
<a href="#" onclick="setDefault('{{$paymentMethod['id']}}')">({{trans('texts.use_for_auto_bill')}})</a>
@elseif($paymentMethod->payment_type_id != PAYMENT_TYPE_ACH || $paymentMethod->status == PAYMENT_METHOD_STATUS_VERIFIED)
<a href="#" onclick="setDefault('{{$paymentMethod->public_id}}')">({{trans('texts.use_for_auto_bill')}})</a>
@endif
<a href="javasript::void" class="payment_method_remove" onclick="removePaymentMethod('{{$paymentMethod['id']}}')">&times;</a>
<a href="javasript::void" class="payment_method_remove" onclick="removePaymentMethod('{{$paymentMethod->public_id}}')">&times;</a>
</div>
@endforeach
@endif