Merge pull request #7981 from turbo124/v5-develop

Refactor Stripe Webhook to support all API Versions.
This commit is contained in:
David Bomba 2022-11-26 10:18:12 +11:00 committed by GitHub
commit d82905a1dd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 121 additions and 2634 deletions

View File

@ -48,7 +48,7 @@ class Kernel extends ConsoleKernel
$schedule->job(new VersionCheck)->daily(); $schedule->job(new VersionCheck)->daily();
/* Checks and cleans redundant files */ /* Checks and cleans redundant files */
$schedule->job(new DiskCleanup)->daily()->withoutOverlapping(); $schedule->job(new DiskCleanup)->dailyAt('02:10')->withoutOverlapping();
/* Send reminders */ /* Send reminders */
$schedule->job(new ReminderJob)->hourly()->withoutOverlapping(); $schedule->job(new ReminderJob)->hourly()->withoutOverlapping();
@ -63,7 +63,7 @@ class Kernel extends ConsoleKernel
$schedule->job(new UpdateExchangeRates)->dailyAt('23:30')->withoutOverlapping(); $schedule->job(new UpdateExchangeRates)->dailyAt('23:30')->withoutOverlapping();
/* Runs cleanup code for subscriptions */ /* Runs cleanup code for subscriptions */
$schedule->job(new SubscriptionCron)->daily()->withoutOverlapping(); $schedule->job(new SubscriptionCron)->dailyAt('00:01')->withoutOverlapping();
/* Sends recurring invoices*/ /* Sends recurring invoices*/
$schedule->job(new RecurringInvoicesCron)->hourly()->withoutOverlapping(); $schedule->job(new RecurringInvoicesCron)->hourly()->withoutOverlapping();
@ -72,22 +72,22 @@ class Kernel extends ConsoleKernel
$schedule->job(new RecurringExpensesCron)->dailyAt('00:10')->withoutOverlapping(); $schedule->job(new RecurringExpensesCron)->dailyAt('00:10')->withoutOverlapping();
/* Fires notifications for expired Quotes */ /* Fires notifications for expired Quotes */
$schedule->job(new QuoteCheckExpired)->dailyAt('05:00')->withoutOverlapping(); $schedule->job(new QuoteCheckExpired)->dailyAt('05:10')->withoutOverlapping();
/* Performs auto billing */ /* Performs auto billing */
$schedule->job(new AutoBillCron)->dailyAt('06:00')->withoutOverlapping(); $schedule->job(new AutoBillCron)->dailyAt('06:20')->withoutOverlapping();
/* Checks the status of the scheduler */ /* Checks the status of the scheduler */
$schedule->job(new SchedulerCheck)->daily()->withoutOverlapping(); $schedule->job(new SchedulerCheck)->dailyAt('01:10')->withoutOverlapping();
/* Checks for scheduled tasks */ /* Checks for scheduled tasks */
$schedule->job(new TaskScheduler())->daily()->withoutOverlapping(); $schedule->job(new TaskScheduler())->dailyAt('06:50')->withoutOverlapping();
/* Performs system maintenance such as pruning the backup table */ /* Performs system maintenance such as pruning the backup table */
$schedule->job(new SystemMaintenance)->weekly()->withoutOverlapping(); $schedule->job(new SystemMaintenance)->weekly()->withoutOverlapping();
/* Pulls in bank transactions from third party services */ /* Pulls in bank transactions from third party services */
$schedule->job(new BankTransactionSync)->dailyAt('04:00')->withoutOverlapping(); $schedule->job(new BankTransactionSync)->dailyAt('04:10')->withoutOverlapping();
if (Ninja::isSelfHost()) { if (Ninja::isSelfHost()) {

View File

@ -31,7 +31,7 @@ class UpdateTaskRequest extends Request
public function authorize() : bool public function authorize() : bool
{ {
//prevent locked tasks from updating //prevent locked tasks from updating
if($this->task->invoice_lock && $this->task->invoice_id) if($this->task->invoice_id && $this->task->company->invoice_task_lock)
return false; return false;
return auth()->user()->can('edit', $this->task); return auth()->user()->can('edit', $this->task);

View File

@ -40,7 +40,6 @@ class Task extends BaseModel
'number', 'number',
'is_date_based', 'is_date_based',
'status_order', 'status_order',
'invoice_lock'
]; ];
protected $touches = []; protected $touches = [];

View File

@ -138,7 +138,7 @@ class CreditCard
'payment_method' => $this->stripe->payment_hash->data->server_response->payment_method, 'payment_method' => $this->stripe->payment_hash->data->server_response->payment_method,
'payment_type' => PaymentType::parseCardType(strtolower($stripe_method->card->brand)) ?: PaymentType::CREDIT_CARD_OTHER, 'payment_type' => PaymentType::parseCardType(strtolower($stripe_method->card->brand)) ?: PaymentType::CREDIT_CARD_OTHER,
'amount' => $this->stripe->convertFromStripeAmount($this->stripe->payment_hash->data->server_response->amount, $this->stripe->client->currency()->precision, $this->stripe->client->currency()), 'amount' => $this->stripe->convertFromStripeAmount($this->stripe->payment_hash->data->server_response->amount, $this->stripe->client->currency()->precision, $this->stripe->client->currency()),
'transaction_reference' => optional($this->stripe->payment_hash->data->payment_intent->charges->data[0])->id, 'transaction_reference' => isset($this->stripe->payment_hash->data->payment_intent->latest_charge) ? $this->stripe->payment_hash->data->payment_intent->latest_charge : optional($this->stripe->payment_hash->data->payment_intent->charges->data[0])->id,
'gateway_type_id' => GatewayType::CREDIT_CARD, 'gateway_type_id' => GatewayType::CREDIT_CARD,
]; ];

View File

@ -92,75 +92,91 @@ class PaymentIntentWebhook implements ShouldQueue
if($this->payment_completed) if($this->payment_completed)
return; return;
$company_gateway = CompanyGateway::find($this->company_gateway_id);
$stripe_driver = $company_gateway->driver()->init();
if(isset($this->stripe_request['object']['charges']) && optional($this->stripe_request['object']['charges']['data'][0])['id']){ $charge_id = false;
$company = Company::where('company_key', $this->company_key)->first(); if(isset($this->stripe_request['object']['charges']) && optional($this->stripe_request['object']['charges']['data'][0])['id'])
$charge_id = $this->stripe_request['object']['charges']['data'][0]['id']; // API VERSION 2018
$payment = Payment::query() elseif (isset($this->stripe_request['object']['latest_charge']))
->where('company_id', $company->id) $charge_id = $this->stripe_request['object']['latest_charge']; // API VERSION 2022-11-15
->where('transaction_reference', $this->stripe_request['object']['charges']['data'][0]['id'])
->first();
//return early
if($payment && $payment->status_id == Payment::STATUS_COMPLETED){
nlog(" payment found and status correct - returning ");
return;
}
elseif($payment){
$payment->status_id = Payment::STATUS_COMPLETED;
$payment->save();
}
$hash = optional($this->stripe_request['object']['charges']['data'][0]['metadata'])['payment_hash']; if(!$charge_id){
nlog("could not resolve charge");
$payment_hash = PaymentHash::where('hash', $hash)->first(); return;
if(!$payment_hash)
return;
nlog("payment intent");
nlog($this->stripe_request);
if(array_key_exists('allowed_source_types', $this->stripe_request['object']) && optional($this->stripe_request['object']['charges']['data'][0]['metadata']['payment_hash']) && in_array('card', $this->stripe_request['object']['allowed_source_types']))
{
nlog("hash found");
$hash = $this->stripe_request['object']['charges']['data'][0]['metadata']['payment_hash'];
$payment_hash = PaymentHash::where('hash', $hash)->first();
$invoice = Invoice::with('client')->find($payment_hash->fee_invoice_id);
$client = $invoice->client;
$this->updateCreditCardPayment($payment_hash, $client);
}
elseif(array_key_exists('payment_method_types', $this->stripe_request['object']) && optional($this->stripe_request['object']['charges']['data'][0]['metadata']['payment_hash']) && in_array('card', $this->stripe_request['object']['payment_method_types']))
{
nlog("hash found");
$hash = $this->stripe_request['object']['charges']['data'][0]['metadata']['payment_hash'];
$payment_hash = PaymentHash::where('hash', $hash)->first();
$invoice = Invoice::with('client')->find($payment_hash->fee_invoice_id);
$client = $invoice->client;
$this->updateCreditCardPayment($payment_hash, $client);
}
elseif(array_key_exists('payment_method_types', $this->stripe_request['object']) && optional($this->stripe_request['object']['charges']['data'][0]['metadata']['payment_hash']) && in_array('us_bank_account', $this->stripe_request['object']['payment_method_types']))
{
nlog("hash found");
$hash = $this->stripe_request['object']['charges']['data'][0]['metadata']['payment_hash'];
$payment_hash = PaymentHash::where('hash', $hash)->first();
$invoice = Invoice::with('client')->find($payment_hash->fee_invoice_id);
$client = $invoice->client;
$this->updateAchPayment($payment_hash, $client);
}
} }
$pi = \Stripe\PaymentIntent::retrieve($this->stripe_request['object']['id'], $stripe_driver->stripe_connect_auth);
$charge = \Stripe\Charge::retrieve($charge_id, $stripe_driver->stripe_connect_auth);
if(!$charge)
{
nlog("no charge found");
nlog($this->stripe_request);
return;
}
$company = Company::where('company_key', $this->company_key)->first();
$payment = Payment::query()
->where('company_id', $company->id)
->where('transaction_reference', $charge['id'])
->first();
//return early
if($payment && $payment->status_id == Payment::STATUS_COMPLETED){
nlog(" payment found and status correct - returning ");
return;
}
elseif($payment){
$payment->status_id = Payment::STATUS_COMPLETED;
$payment->save();
}
$hash = isset($charge['metadata']['payment_hash']) ? $charge['metadata']['payment_hash'] : false;
$payment_hash = PaymentHash::where('hash', $hash)->first();
if(!$payment_hash)
return;
$stripe_driver->client = $payment_hash->fee_invoice->client;
$meta = [
'gateway_type_id' => $pi['metadata']['gateway_type_id'],
'transaction_reference' => $charge['id'],
'customer' => $charge['customer'],
'payment_method' => $charge['payment_method'],
'card_details' => isset($charge['payment_method_details']['card']['brand']) ? $charge['payment_method_details']['card']['brand'] : PaymentType::CREDIT_CARD_OTHER
];
if(isset($pi['allowed_source_types']) && in_array('card', $pi['allowed_source_types']))
{
$invoice = Invoice::with('client')->find($payment_hash->fee_invoice_id);
$client = $invoice->client;
$this->updateCreditCardPayment($payment_hash, $client, $meta);
}
elseif(isset($pi['payment_method_types']) && in_array('card', $pi['payment_method_types']))
{
$invoice = Invoice::with('client')->find($payment_hash->fee_invoice_id);
$client = $invoice->client;
$this->updateCreditCardPayment($payment_hash, $client, $meta);
}
elseif(isset($pi['payment_method_types']) && in_array('us_bank_account', $pi['payment_method_types']))
{
$invoice = Invoice::with('client')->find($payment_hash->fee_invoice_id);
$client = $invoice->client;
$this->updateAchPayment($payment_hash, $client, $meta);
}
SystemLogger::dispatch( SystemLogger::dispatch(
['response' => $this->stripe_request, 'data' => []], ['response' => $this->stripe_request, 'data' => []],
@ -174,10 +190,10 @@ class PaymentIntentWebhook implements ShouldQueue
} }
private function updateAchPayment($payment_hash, $client) private function updateAchPayment($payment_hash, $client, $meta)
{ {
$company_gateway = CompanyGateway::find($this->company_gateway_id); $company_gateway = CompanyGateway::find($this->company_gateway_id);
$payment_method_type = optional($this->stripe_request['object']['charges']['data'][0]['metadata'])['gateway_type_id']; $payment_method_type = $meta['gateway_type_id'];
$driver = $company_gateway->driver($client)->init()->setPaymentMethod($payment_method_type); $driver = $company_gateway->driver($client)->init()->setPaymentMethod($payment_method_type);
$payment_hash->data = array_merge((array) $payment_hash->data, $this->stripe_request); $payment_hash->data = array_merge((array) $payment_hash->data, $this->stripe_request);
@ -188,7 +204,7 @@ class PaymentIntentWebhook implements ShouldQueue
'payment_method' => $payment_hash->data->object->payment_method, 'payment_method' => $payment_hash->data->object->payment_method,
'payment_type' => PaymentType::ACH, 'payment_type' => PaymentType::ACH,
'amount' => $payment_hash->data->amount_with_fee, 'amount' => $payment_hash->data->amount_with_fee,
'transaction_reference' => $this->stripe_request['object']['charges']['data'][0]['id'], 'transaction_reference' => $meta['transaction_reference'],
'gateway_type_id' => GatewayType::BANK_TRANSFER, 'gateway_type_id' => GatewayType::BANK_TRANSFER,
]; ];
@ -205,9 +221,9 @@ class PaymentIntentWebhook implements ShouldQueue
try { try {
$customer = $driver->getCustomer($this->stripe_request['object']['charges']['data'][0]['customer']); $customer = $driver->getCustomer($meta['customer']);
$method = $driver->getStripePaymentMethod($this->stripe_request['object']['charges']['data'][0]['payment_method']); $method = $driver->getStripePaymentMethod($meta['payment_method']);
$payment_method = $this->stripe_request['object']['charges']['data'][0]['payment_method']; $payment_method = $meta['payment_method'];
$token_exists = ClientGatewayToken::where([ $token_exists = ClientGatewayToken::where([
'gateway_customer_reference' => $customer->id, 'gateway_customer_reference' => $customer->id,
@ -249,10 +265,10 @@ class PaymentIntentWebhook implements ShouldQueue
} }
private function updateCreditCardPayment($payment_hash, $client) private function updateCreditCardPayment($payment_hash, $client, $meta)
{ {
$company_gateway = CompanyGateway::find($this->company_gateway_id); $company_gateway = CompanyGateway::find($this->company_gateway_id);
$payment_method_type = optional($this->stripe_request['object']['charges']['data'][0]['metadata'])['gateway_type_id']; $payment_method_type = $meta['gateway_type_id'];
$driver = $company_gateway->driver($client)->init()->setPaymentMethod($payment_method_type); $driver = $company_gateway->driver($client)->init()->setPaymentMethod($payment_method_type);
$payment_hash->data = array_merge((array) $payment_hash->data, $this->stripe_request); $payment_hash->data = array_merge((array) $payment_hash->data, $this->stripe_request);
@ -261,9 +277,9 @@ class PaymentIntentWebhook implements ShouldQueue
$data = [ $data = [
'payment_method' => $payment_hash->data->object->payment_method, 'payment_method' => $payment_hash->data->object->payment_method,
'payment_type' => PaymentType::parseCardType(strtolower(optional($this->stripe_request['object']['charges']['data'][0]['payment_method_details']['card'])['brand'])) ?: PaymentType::CREDIT_CARD_OTHER, 'payment_type' => PaymentType::parseCardType(strtolower($meta['card_details'])) ?: PaymentType::CREDIT_CARD_OTHER,
'amount' => $payment_hash->data->amount_with_fee, 'amount' => $payment_hash->data->amount_with_fee,
'transaction_reference' => $this->stripe_request['object']['charges']['data'][0]['id'], 'transaction_reference' => $meta['transaction_reference'],
'gateway_type_id' => GatewayType::CREDIT_CARD, 'gateway_type_id' => GatewayType::CREDIT_CARD,
]; ];

View File

@ -116,11 +116,14 @@ class StripePaymentDriver extends BaseDriver
throw new StripeConnectFailure('Stripe Connect has not been configured'); throw new StripeConnectFailure('Stripe Connect has not been configured');
} }
} else { } else {
$this->stripe = new StripeClient( $this->stripe = new StripeClient(
$this->company_gateway->getConfigField('apiKey') $this->company_gateway->getConfigField('apiKey')
); );
Stripe::setApiKey($this->company_gateway->getConfigField('apiKey')); Stripe::setApiKey($this->company_gateway->getConfigField('apiKey'));
// Stripe::setApiVersion('2022-11-15');
} }
return $this; return $this;

View File

@ -92,7 +92,6 @@ class TaskTransformer extends EntityTransformer
'status_sort_order' => (int) $task->status_sort_order, //deprecated 5.0.34 'status_sort_order' => (int) $task->status_sort_order, //deprecated 5.0.34
'is_date_based' => (bool) $task->is_date_based, 'is_date_based' => (bool) $task->is_date_based,
'status_order' => is_null($task->status_order) ? null : (int) $task->status_order, 'status_order' => is_null($task->status_order) ? null : (int) $task->status_order,
'invoice_lock' => (bool) $task->invoice_lock,
]; ];
} }
} }

View File

@ -14,10 +14,6 @@ return new class extends Migration
*/ */
public function up() public function up()
{ {
Schema::table('tasks', function (Blueprint $table)
{
$table->boolean('invoice_lock')->default(false);
});
Schema::table('companies', function (Blueprint $table) Schema::table('companies', function (Blueprint $table)
{ {

File diff suppressed because it is too large Load Diff

View File

@ -81,9 +81,9 @@ class TaskApiTest extends TestCase
$response->assertStatus(200); $response->assertStatus(200);
$task = Task::find($this->decodePrimaryKey($arr['data']['id'])); $task = Task::find($this->decodePrimaryKey($arr['data']['id']));
$task->invoice_lock =true; $task->company->invoice_task_lock = true;
$task->invoice_id = $this->invoice->id; $task->invoice_id = $this->invoice->id;
$task->save(); $task->push();
$response = $this->withHeaders([ $response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'), 'X-API-SECRET' => config('ninja.api_secret'),
@ -97,32 +97,31 @@ class TaskApiTest extends TestCase
} }
public function testTaskLocking() // public function testTaskLocking()
{ // {
$data = [ // $data = [
'timelog' => [[1,2],[3,4]], // 'timelog' => [[1,2],[3,4]],
'invoice_lock' => true // ];
];
$response = $this->withHeaders([ // $response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'), // 'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $this->token, // 'X-API-TOKEN' => $this->token,
])->post('/api/v1/tasks', $data); // ])->post('/api/v1/tasks', $data);
$arr = $response->json(); // $arr = $response->json();
$response->assertStatus(200); // $response->assertStatus(200);
$response = $this->withHeaders([ // $response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'), // 'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $this->token, // 'X-API-TOKEN' => $this->token,
])->putJson('/api/v1/tasks/' . $arr['data']['id'], $data); // ])->putJson('/api/v1/tasks/' . $arr['data']['id'], $data);
$arr = $response->json(); // $arr = $response->json();
$response->assertStatus(200); // $response->assertStatus(200);
} // }