1
0
mirror of https://github.com/invoiceninja/invoiceninja.git synced 2024-09-20 00:11:35 +02:00

Fixes for Rotessa Import Customers + Payments

This commit is contained in:
David Bomba 2024-08-02 14:44:12 +10:00
parent 5f0caf370d
commit fe891a4293
7 changed files with 47 additions and 168 deletions

View File

@ -33,10 +33,7 @@ class AccountComponent extends Component
"authorization_type" => 'Online'
];
public array $account;
public function __construct(array $account) {
$this->account = $account;
public function __construct(public array $account) {
$this->attributes = $this->newAttributeBag(Arr::only($this->account, $this->fields) );
}

View File

@ -25,10 +25,7 @@ class AddressComponent extends Component
'country' => 'US'
];
public array $address;
public function __construct(array $address) {
$this->address = $address;
public function __construct(public array $address) {
if(strlen($this->address['state']) > 2 ) {
$this->address['state'] = $this->address['country'] == 'US' ? array_search($this->address['state'], USStates::$states) : CAProvinces::getAbbreviation($this->address['state']);
}

View File

@ -18,9 +18,9 @@ class ContactComponent extends Component
$contact = collect($contact->client->contacts->firstWhere('is_primary', 1)->toArray())->merge([
'home_phone' =>$contact->client->phone,
'custom_identifier' => $contact->client->number,
'custom_identifier' => $contact->client->client_hash,
'name' =>$contact->client->name,
'id' => $contact->client->contact_key,
'id' => null,
] )->all();
$this->attributes = $this->newAttributeBag(Arr::only($contact, $this->fields) );

View File

@ -1,123 +0,0 @@
<?php
namespace App\Http\ViewComposers\Components;
use App\DataProviders\CAProvinces;
use App\DataProviders\USStates;
use Illuminate\View\Component;
use App\Models\ClientContact;
use Illuminate\Support\Arr;
use Illuminate\View\View;
// Contact Component
class ContactComponent extends Component
{
public function __construct(ClientContact $contact) {
$contact = collect($contact->client->contacts->firstWhere('is_primary', 1)->toArray())->merge([
'home_phone' =>$contact->client->phone,
'custom_identifier' => $contact->client->number,
'name' =>$contact->client->name,
'id' => null
] )->all();
$this->attributes = $this->newAttributeBag(Arr::only($contact, $this->fields) );
}
private $fields = [
'name',
'email',
'home_phone',
'phone',
'custom_identifier',
'customer_type' ,
'id'
];
private $defaults = [
'customer_type' => "Business",
'customer_identifier' => null,
'id' => null
];
public function render()
{
return render('gateways.rotessa.components.contact', array_merge($this->defaults, $this->attributes->getAttributes() ) );
}
}
// Address Component
class AddressComponent extends Component
{
private $fields = [
'address_1',
'address_2',
'city',
'postal_code',
'province_code',
'country'
];
private $defaults = [
'country' => 'US'
];
public array $address;
public function __construct(array $address) {
$this->address = $address;
if(strlen($this->address['state']) > 2 ) {
$this->address['state'] = $this->address['country'] == 'US' ? array_search($this->address['state'], USStates::$states) : CAProvinces::getAbbreviation($this->address['state']);
}
$this->attributes = $this->newAttributeBag(
Arr::only(Arr::mapWithKeys($this->address, function ($item, $key) {
return in_array($key, ['address1','address2','state'])?[ (['address1'=>'address_1','address2'=>'address_2','state'=>'province_code'])[$key] => $item ] :[ $key => $item ];
}),
$this->fields) );
}
public function render()
{
return render('gateways.rotessa.components.address',array_merge( $this->defaults, $this->attributes->getAttributes() ) );
}
}
// AmericanBankInfo Component
class AccountComponent extends Component
{
private $fields = [
'bank_account_type',
'routing_number',
'institution_number',
'transit_number',
'bank_name',
'country',
'account_number'
];
private $defaults = [
'bank_account_type' => null,
'routing_number' => null,
'institution_number' => null,
'transit_number' => null,
'bank_name' => ' ',
'account_number' => null,
'country' => 'US',
"authorization_type" => 'Online'
];
public array $account;
public function __construct(array $account) {
$this->account = $account;
$this->attributes = $this->newAttributeBag(Arr::only($this->account, $this->fields) );
}
public function render()
{
return render('gateways.rotessa.components.account', array_merge($this->attributes->getAttributes(), $this->defaults) );
}
}

View File

@ -37,11 +37,9 @@ use App\Http\Requests\ClientPortal\Payments\PaymentResponseRequest;
class PaymentMethod implements MethodInterface
{
protected RotessaPaymentDriver $rotessa;
public function __construct(RotessaPaymentDriver $rotessa)
public function __construct(protected RotessaPaymentDriver $rotessa)
{
$this->rotessa = $rotessa;
$this->rotessa->init();
}
@ -60,9 +58,6 @@ class PaymentMethod implements MethodInterface
'id' => null
] )->all();
$data['gateway'] = $this->rotessa;
// Set gateway type according to client country
// $data['gateway_type_id'] = $data['client']->country->iso_3166_2 == 'US' ? GatewayType::BANK_TRANSFER : ( $data['client']->country->iso_3166_2 == 'CA' ? GatewayType::ACSS : (int) request('method'));
// TODO: detect GatewayType based on client country USA vs CAN
$data['gateway_type_id'] = GatewayType::ACSS ;
$data['account'] = [
'routing_number' => $data['client']->routing_id,
@ -104,6 +99,7 @@ class PaymentMethod implements MethodInterface
'customer_id'=>['required_without:custom_identifier','integer'],
]);
$customer = new Customer( ['address' => $request->only('address_1','address_2','city','postal_code','province_code','country'), 'custom_identifier' => $request->input('custom_identifier') ] + $request->all());
$this->rotessa->findOrCreateCustomer($customer->resolve());
return redirect()->route('client.payment_methods.index')->withMessage(ctrans('texts.payment_method_added'));
@ -142,8 +138,10 @@ class PaymentMethod implements MethodInterface
*/
public function paymentResponse(PaymentResponseRequest $request)
{
$response= null;
$customer = null;
try {
$request->validate([
'source' => ['required','string','exists:client_gateway_tokens,token'],
@ -153,17 +151,20 @@ class PaymentMethod implements MethodInterface
$customer = ClientGatewayToken::query()
->where('company_gateway_id', $this->rotessa->company_gateway->id)
->where('client_id', $this->rotessa->client->id)
->where('is_deleted', 0)
->where('token', $request->input('source'))
->first();
if(!$customer) throw new \Exception('Client gateway token not found!', SystemLog::TYPE_ROTESSA);
$transaction = new Transaction($request->only('frequency' ,'installments','amount','process_date') + ['comment' => $this->rotessa->getDescription(false) ]);
$transaction->additional(['customer_id' => $customer->gateway_customer_reference]);
$transaction = array_filter( $transaction->resolve());
$response = $this->rotessa->gateway->capture($transaction)->send();
if(!$response->isSuccessful()) throw new \Exception($response->getMessage(), (int) $response->getCode());
return $this->processPendingPayment($response->getParameter('id'), (float) $response->getParameter('amount'), (int) $customer->gateway_type_id , $customer->token);
return $this->processPendingPayment($response->getParameter('id'), (float) $response->getParameter('amount'), PaymentType::ACSS , $customer->token);
} catch(\Throwable $e) {
$this->processUnsuccessfulPayment( new InvalidResponseException($e->getMessage(), (int) $e->getCode()) );
}
@ -194,7 +195,7 @@ class PaymentMethod implements MethodInterface
/**
* Handle unsuccessful payment for Rotessa.
*
* @param Exception $exception
* @param \Exception $exception
* @throws PaymentFailed
* @return void
*/

View File

@ -11,6 +11,7 @@
namespace App\PaymentDrivers;
use App\DataMapper\ClientSettings;
use Omnipay\Omnipay;
use App\Models\Client;
use App\Models\Payment;
@ -183,6 +184,9 @@ class RotessaPaymentDriver extends BaseDriver
"updated_at": "2015-02-10T23:50:45.000-06:00"
}
*/
$settings = ClientSettings::defaults();
$settings->currency_id = $this->company_gateway->company->getSetting('currency_id');
$client = (\App\Factory\ClientFactory::create($this->company_gateway->company_id, $this->company_gateway->user_id))->fill(
[
'address1' => $customer->address['address_1'] ?? '',
@ -192,7 +196,8 @@ class RotessaPaymentDriver extends BaseDriver
'state' => $customer->address['province_code'] ?? '',
'country_id' => empty($customer->transit_number) ? 840 : 124,
'routing_id' => empty(($r = $customer->routing_number))? null : $r,
"number" => str_pad($customer->account_number,3,'0',STR_PAD_LEFT)
"number" => str_pad($customer->account_number,3,'0',STR_PAD_LEFT),
"settings" => $settings,
]
);
$client->saveQuietly();
@ -234,8 +239,9 @@ class RotessaPaymentDriver extends BaseDriver
$existing = ClientGatewayToken::query()
->where('company_gateway_id', $this->company_gateway->id)
->where('client_id', $this->client->id)
->where('is_deleted',0)
->orWhere(function (Builder $query) use ($data) {
$query->where('token', encrypt(join(".", Arr::only($data, 'id','custom_identifier'))) )
$query->where('token', join(".", Arr::only($data, ['id','custom_identifier'])))
->where('gateway_customer_reference', Arr::only($data,'id'));
})
->exists();
@ -246,14 +252,15 @@ class RotessaPaymentDriver extends BaseDriver
$customer = new Customer($result->getData());
$data = array_filter($customer->resolve());
}
// $payment_method_id = Arr::has($data,'address.postal_code') && ((int) $data['address']['postal_code'])? GatewayType::BANK_TRANSFER: GatewayType::ACSS;
// TODO: Check/ Validate postal code between USA vs CAN
$payment_method_id = GatewayType::ACSS;
$gateway_token = $this->storeGatewayToken( [
'payment_meta' => $data + ['brand' => 'Rotessa', 'last4' => $data['bank_name'], 'type' => $data['bank_account_type'] ],
'token' => encrypt(join(".", Arr::only($data, 'id','custom_identifier'))),
'payment_meta' => $data + ['brand' => 'Bank Transfer', 'last4' => substr($data['account_number'], -4), 'type' => GatewayType::ACSS ],
'token' => join(".", Arr::only($data, ['id','custom_identifier'])),
'payment_method_id' => $payment_method_id ,
], ['gateway_customer_reference' =>
$data['id']

46
composer.lock generated
View File

@ -535,16 +535,16 @@
},
{
"name": "aws/aws-sdk-php",
"version": "3.316.10",
"version": "3.317.0",
"source": {
"type": "git",
"url": "https://github.com/aws/aws-sdk-php.git",
"reference": "eeb8df6ff6caa428e8bcd631ad2a96430900a249"
"reference": "6d46c8e00c22f66349b8a509bd2c5ced72cceff2"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/eeb8df6ff6caa428e8bcd631ad2a96430900a249",
"reference": "eeb8df6ff6caa428e8bcd631ad2a96430900a249",
"url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/6d46c8e00c22f66349b8a509bd2c5ced72cceff2",
"reference": "6d46c8e00c22f66349b8a509bd2c5ced72cceff2",
"shasum": ""
},
"require": {
@ -624,9 +624,9 @@
"support": {
"forum": "https://forums.aws.amazon.com/forum.jspa?forumID=80",
"issues": "https://github.com/aws/aws-sdk-php/issues",
"source": "https://github.com/aws/aws-sdk-php/tree/3.316.10"
"source": "https://github.com/aws/aws-sdk-php/tree/3.317.0"
},
"time": "2024-07-30T18:10:20+00:00"
"time": "2024-08-01T18:12:34+00:00"
},
{
"name": "bacon/bacon-qr-code",
@ -4758,16 +4758,16 @@
},
{
"name": "laravel/pint",
"version": "v1.17.0",
"version": "v1.17.1",
"source": {
"type": "git",
"url": "https://github.com/laravel/pint.git",
"reference": "4dba80c1de4b81dc4c4fb10ea6f4781495eb29f5"
"reference": "b5b6f716db298671c1dfea5b1082ec2c0ae7064f"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/laravel/pint/zipball/4dba80c1de4b81dc4c4fb10ea6f4781495eb29f5",
"reference": "4dba80c1de4b81dc4c4fb10ea6f4781495eb29f5",
"url": "https://api.github.com/repos/laravel/pint/zipball/b5b6f716db298671c1dfea5b1082ec2c0ae7064f",
"reference": "b5b6f716db298671c1dfea5b1082ec2c0ae7064f",
"shasum": ""
},
"require": {
@ -4820,7 +4820,7 @@
"issues": "https://github.com/laravel/pint/issues",
"source": "https://github.com/laravel/pint"
},
"time": "2024-07-23T16:40:20+00:00"
"time": "2024-08-01T09:06:33+00:00"
},
{
"name": "laravel/prompts",
@ -16968,16 +16968,16 @@
},
{
"name": "phpstan/phpstan",
"version": "1.11.8",
"version": "1.11.9",
"source": {
"type": "git",
"url": "https://github.com/phpstan/phpstan.git",
"reference": "6adbd118e6c0515dd2f36b06cde1d6da40f1b8ec"
"reference": "e370bcddadaede0c1716338b262346f40d296f82"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/6adbd118e6c0515dd2f36b06cde1d6da40f1b8ec",
"reference": "6adbd118e6c0515dd2f36b06cde1d6da40f1b8ec",
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/e370bcddadaede0c1716338b262346f40d296f82",
"reference": "e370bcddadaede0c1716338b262346f40d296f82",
"shasum": ""
},
"require": {
@ -17022,7 +17022,7 @@
"type": "github"
}
],
"time": "2024-07-24T07:01:22+00:00"
"time": "2024-08-01T16:25:18+00:00"
},
{
"name": "phpunit/php-code-coverage",
@ -19031,16 +19031,16 @@
},
{
"name": "spatie/flare-client-php",
"version": "1.7.0",
"version": "1.8.0",
"source": {
"type": "git",
"url": "https://github.com/spatie/flare-client-php.git",
"reference": "097040ff51e660e0f6fc863684ac4b02c93fa234"
"reference": "180f8ca4c0d0d6fc51477bd8c53ce37ab5a96122"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/spatie/flare-client-php/zipball/097040ff51e660e0f6fc863684ac4b02c93fa234",
"reference": "097040ff51e660e0f6fc863684ac4b02c93fa234",
"url": "https://api.github.com/repos/spatie/flare-client-php/zipball/180f8ca4c0d0d6fc51477bd8c53ce37ab5a96122",
"reference": "180f8ca4c0d0d6fc51477bd8c53ce37ab5a96122",
"shasum": ""
},
"require": {
@ -19058,7 +19058,7 @@
"phpstan/extension-installer": "^1.1",
"phpstan/phpstan-deprecation-rules": "^1.0",
"phpstan/phpstan-phpunit": "^1.0",
"spatie/phpunit-snapshot-assertions": "^4.0|^5.0"
"spatie/pest-plugin-snapshots": "^1.0|^2.0"
},
"type": "library",
"extra": {
@ -19088,7 +19088,7 @@
],
"support": {
"issues": "https://github.com/spatie/flare-client-php/issues",
"source": "https://github.com/spatie/flare-client-php/tree/1.7.0"
"source": "https://github.com/spatie/flare-client-php/tree/1.8.0"
},
"funding": [
{
@ -19096,7 +19096,7 @@
"type": "github"
}
],
"time": "2024-06-12T14:39:14+00:00"
"time": "2024-08-01T08:27:26+00:00"
},
{
"name": "spatie/ignition",