diff --git a/VERSION.txt b/VERSION.txt index 078558401440..bb27662b9009 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1 +1 @@ -5.6.19 \ No newline at end of file +5.6.20 \ No newline at end of file diff --git a/app/Http/Controllers/CompanyGatewayController.php b/app/Http/Controllers/CompanyGatewayController.php index b641344be700..5e9fed6bfd81 100644 --- a/app/Http/Controllers/CompanyGatewayController.php +++ b/app/Http/Controllers/CompanyGatewayController.php @@ -24,6 +24,7 @@ use App\Http\Requests\CompanyGateway\UpdateCompanyGatewayRequest; use App\Jobs\Util\ApplePayDomain; use App\Models\Client; use App\Models\CompanyGateway; +use App\PaymentDrivers\CheckoutCom\CheckoutSetupWebhook; use App\PaymentDrivers\Stripe\Jobs\StripeWebhook; use App\Repositories\CompanyRepository; use App\Transformers\CompanyGatewayTransformer; @@ -49,6 +50,8 @@ class CompanyGatewayController extends BaseController private array $stripe_keys = ['d14dd26a47cecc30fdd65700bfb67b34', 'd14dd26a37cecc30fdd65700bfb55b23']; + private string $checkout_key = '3758e7f7c6f4cecf0f4f348b9a00f456'; + /** * CompanyGatewayController constructor. * @param CompanyRepository $company_repo @@ -211,6 +214,9 @@ class CompanyGatewayController extends BaseController if (in_array($company_gateway->gateway_key, $this->stripe_keys)) { StripeWebhook::dispatch($company_gateway->company->company_key, $company_gateway->id); } + elseif($company_gateway->gateway_key == $this->checkout_key) { + CheckoutSetupWebhook::dispatch($company_gateway->company->company_key, $company_gateway->id); + } return $this->itemResponse($company_gateway); } @@ -382,8 +388,10 @@ class CompanyGatewayController extends BaseController $company_gateway->save(); - // ApplePayDomain::dispatch($company_gateway, $company_gateway->company->db); - + if($company_gateway->gateway_key == $this->checkout_key) { + CheckoutSetupWebhook::dispatch($company_gateway->company->company_key, $company_gateway->fresh()->id); + } + return $this->itemResponse($company_gateway); } diff --git a/app/Http/Controllers/ImportController.php b/app/Http/Controllers/ImportController.php index 0d42d74a552b..0bcbabde60b7 100644 --- a/app/Http/Controllers/ImportController.php +++ b/app/Http/Controllers/ImportController.php @@ -97,6 +97,8 @@ class ImportController extends Controller ]; } + $data = mb_convert_encoding($data, 'UTF-8', 'UTF-8'); + return response()->json($data); } diff --git a/app/Http/Requests/RecurringInvoice/StoreRecurringInvoiceRequest.php b/app/Http/Requests/RecurringInvoice/StoreRecurringInvoiceRequest.php index 11c266ac8f46..6a941b1fa394 100644 --- a/app/Http/Requests/RecurringInvoice/StoreRecurringInvoiceRequest.php +++ b/app/Http/Requests/RecurringInvoice/StoreRecurringInvoiceRequest.php @@ -144,7 +144,7 @@ class StoreRecurringInvoiceRequest extends Request unset($input['number']); } - if (array_key_exists('exchange_rate', $input) && is_null($input['exchange_rate'])) { + if (array_key_exists('exchange_rate', $input) && (is_null($input['exchange_rate']) || $input['exchange_rate'] == 0)) { $input['exchange_rate'] = 1; } diff --git a/app/Http/Requests/RecurringInvoice/UpdateRecurringInvoiceRequest.php b/app/Http/Requests/RecurringInvoice/UpdateRecurringInvoiceRequest.php index 26cc4e2f44de..a7d54fd235e2 100644 --- a/app/Http/Requests/RecurringInvoice/UpdateRecurringInvoiceRequest.php +++ b/app/Http/Requests/RecurringInvoice/UpdateRecurringInvoiceRequest.php @@ -122,7 +122,7 @@ class UpdateRecurringInvoiceRequest extends Request unset($input['documents']); } - if (array_key_exists('exchange_rate', $input) && is_null($input['exchange_rate'])) { + if (array_key_exists('exchange_rate', $input) && (is_null($input['exchange_rate']) || $input['exchange_rate'] == 0)) { $input['exchange_rate'] = 1; } diff --git a/app/Import/Providers/BaseImport.php b/app/Import/Providers/BaseImport.php index 5d6ef871dbd9..0019a6efed40 100644 --- a/app/Import/Providers/BaseImport.php +++ b/app/Import/Providers/BaseImport.php @@ -765,7 +765,8 @@ class BaseImport { $keys = array_shift($data); ksort($keys); - +// nlog($data); +// nlog($keys); return array_map(function ($values) use ($keys) { return array_combine($keys, $values); }, $data); diff --git a/app/PaymentDrivers/CheckoutCom/CheckoutSetupWebhook.php b/app/PaymentDrivers/CheckoutCom/CheckoutSetupWebhook.php new file mode 100644 index 000000000000..f9dcc30dcacc --- /dev/null +++ b/app/PaymentDrivers/CheckoutCom/CheckoutSetupWebhook.php @@ -0,0 +1,122 @@ +company_key); + + $company_gateway = CompanyGateway::find($this->company_gateway_id); + + $this->checkout = $company_gateway->driver()->init(); + + $webhook = new Webhook($this->checkout); + + $workflows = $webhook->getWorkFlows(); + + $wf = collect($workflows['data'])->first(function ($workflow) { + return $workflow['name'] == $this->authentication_webhook_name; + }); + + if($wf) + return; + + $this->createAuthenticationWorkflow(); + } + + /** + * Creates an authentication workflow for 3DS + * and also a registration mechanism for payments that have been approved. + * + * @return void + */ + public function createAuthenticationWorkflow() + { + + $signature = new WebhookSignature(); + $signature->key = $this->checkout->company_gateway->company->company_key; + $signature->method = "HMACSHA256"; + + $actionRequest = new WebhookWorkflowActionRequest(); + $actionRequest->url = $this->checkout->company_gateway->webhookUrl(); + $actionRequest->signature = $signature; + + $eventWorkflowConditionRequest = new EventWorkflowConditionRequest(); + $eventWorkflowConditionRequest->events = [ + "gateway" => ["payment_approved"], + "issuing" => ["authorization_approved","authorization_declined"], + ]; + + $request = new CreateWorkflowRequest(); + $request->actions = [$actionRequest]; + $request->conditions = [$eventWorkflowConditionRequest]; + $request->name = $this->authentication_webhook_name; + $request->active = true; + + try { + $response = $this->checkout->gateway->getWorkflowsClient()->createWorkflow($request); + + } catch (CheckoutApiException $e) { + // API error + $error_details = $e->error_details; + $http_status_code = isset($e->http_metadata) ? $e->http_metadata->getStatusCode() : null; + nlog("Checkout WEBHOOK creation error"); + nlog($error_details); + } catch (CheckoutAuthorizationException $e) { + // Bad Invalid authorization + } + + } + + + +} diff --git a/app/PaymentDrivers/CheckoutCom/CheckoutWebhook.php b/app/PaymentDrivers/CheckoutCom/CheckoutWebhook.php new file mode 100644 index 000000000000..fcac5acdc3ac --- /dev/null +++ b/app/PaymentDrivers/CheckoutCom/CheckoutWebhook.php @@ -0,0 +1,117 @@ +company_key); + + $this->company_gateway = CompanyGateway::withTrashed()->find($this->company_gateway_id); + + if(!isset($this->webhook_array['type'])) + nlog("Checkout Webhook type not set"); + + match($this->webhook_array['type']){ + 'payment_approved' => $this->paymentApproved(), + }; + + } + + /** + * { + * "id":"evt_dli6ty4qo5vuxle5wklf5gwbwy","type":"payment_approved","version":"1.0.33","created_on":"2023-07-21T10:03:07.1555904Z", + * "data":{"id":"pay_oqwbsd22kvpuvd35y5fhbdawxa","action_id":"act_buviezur7zsurnsorcgfn63e44","reference":"0014","amount":584168,"auth_code":"113059","currency":"USD","customer":{"id":"cus_6n4yt4q5kf4unn36o5qpbevxhe","email":"cypress@example.com"}, + * "metadata":{"udf1":"Invoice Ninja","udf2":"ofhgiGjyQXbsbUwygURfYFT2C3E7iY7U"},"payment_type":"Regular","processed_on":"2023-07-21T10:02:57.4678165Z","processing":{"acquirer_transaction_id":"645272142084717830381","retrieval_reference_number":"183042259107"},"response_code":"10000","response_summary":"Approved","risk":{"flagged":false,"score":0},"3ds":{"version":"2.2.0","challenged":true,"challenge_indicator":"no_preference","exemption":"none","eci":"05","cavv":"AAABAVIREQAAAAAAAAAAAAAAAAA=","xid":"74afa3ac-25d3-4d95-b815-cefbdd7c8270","downgraded":false,"enrolled":"Y","authentication_response":"Y","flow_type":"challenged"},"scheme_id":"114455763095262", + * "source":{"id":"src_ghavmefpetjellmteqwj5jjcli","type":"card","billing_address":{},"expiry_month":10,"expiry_year":2025,"scheme":"VISA","last_4":"4242","fingerprint":"BD864B08D0B098DD83052A038FD2BA967DF2D48E375AAEEF54E37BC36B385E9A","bin":"424242","card_type":"CREDIT","card_category":"CONSUMER","issuer_country":"GB","product_id":"F","product_type":"Visa Classic","avs_check":"G","cvv_check":"Y"},"balances":{"total_authorized":584168,"total_voided":0,"available_to_void":584168,"total_captured":0,"available_to_capture":584168,"total_refunded":0,"available_to_refund":0},"event_links":{"payment":"https://api.sandbox.checkout.com/payments/pay_oqwbsd22kvpuvd35y5fhbdawxa","payment_actions":"https://api.sandbox.checkout.com/payments/pay_oqwbsd22kvpuvd35y5fhbdawxa/actions","capture":"https://api.sandbox.checkout.com/payments/pay_oqwbsd22kvpuvd35y5fhbdawxa/captures","void":"https://api.sandbox.checkout.com/payments/pay_oqwbsd22kvpuvd35y5fhbdawxa/voids"}},"_links":{"self":{"href":"https://api.sandbox.checkout.com/workflows/events/evt_dli6ty4qo5vuxle5wklf5gwbwy"},"subject":{"href":"https://api.sandbox.checkout.com/workflows/events/subject/pay_oqwbsd22kvpuvd35y5fhbdawxa"},"payment":{"href":"https://api.sandbox.checkout.com/payments/pay_oqwbsd22kvpuvd35y5fhbdawxa"},"payment_actions":{"href":"https://api.sandbox.checkout.com/payments/pay_oqwbsd22kvpuvd35y5fhbdawxa/actions"},"capture":{"href":"https://api.sandbox.checkout.com/payments/pay_oqwbsd22kvpuvd35y5fhbdawxa/captures"},"void":{"href":"https://api.sandbox.checkout.com/payments/pay_oqwbsd22kvpuvd35y5fhbdawxa/voids"}}} + */ + + private function paymentApproved() + { + $payment_object = $this->webhook_array['data']; + + $payment = Payment::withTrashed()->where('transaction_reference', $payment_object['id'])->first(); + + if($payment && $payment->status_id == Payment::STATUS_COMPLETED) + return; + + if($payment){ + $payment->status_id = Payment::STATUS_COMPLETED; + $payment->save(); + return; + } + + if(isset($this->webhook_array['metadata'])) { + + $metadata = $this->webhook_array['metadata']; + + $payment_hash = PaymentHash::where('hash', $metadata['udf2'])->first(); + + $driver = $this->company_gateway->driver($payment_hash->fee_invoice->client)->init()->setPaymentMethod(); + + $payment_hash->data = array_merge((array) $payment_hash->data, $this->webhook_array); + $payment_hash->save(); + $driver->setPaymentHash($payment_hash); + + $data = [ + 'payment_method' => isset($this->webhook_array['source']['id']) ? $this->webhook_array['source']['id'] : '', + 'payment_type' => PaymentType::CREDIT_CARD_OTHER, + 'amount' => $payment_hash->data->raw_value, + 'transaction_reference' => $payment_object['id'], + 'gateway_type_id' => GatewayType::CREDIT_CARD, + ]; + + $payment = $driver->createPayment($data, \App\Models\Payment::STATUS_COMPLETED); + + SystemLogger::dispatch( + ['response' => $this->webhook_array, 'data' => $data], + SystemLog::CATEGORY_GATEWAY_RESPONSE, + SystemLog::EVENT_GATEWAY_SUCCESS, + SystemLog::TYPE_CHECKOUT, + $payment_hash->fee_invoice->client, + $this->company_gateway->company, + ); + + } + + } +} diff --git a/app/PaymentDrivers/CheckoutCom/CreditCard.php b/app/PaymentDrivers/CheckoutCom/CreditCard.php index 6aefc0845180..116123e16796 100644 --- a/app/PaymentDrivers/CheckoutCom/CreditCard.php +++ b/app/PaymentDrivers/CheckoutCom/CreditCard.php @@ -228,7 +228,7 @@ class CreditCard implements MethodInterface $paymentRequest->amount = $this->checkout->payment_hash->data->value; $paymentRequest->reference = substr($this->checkout->getDescription(), 0, 49); $paymentRequest->customer = $this->checkout->getCustomer(); - $paymentRequest->metadata = ['udf1' => 'Invoice Ninja']; + $paymentRequest->metadata = ['udf1' => 'Invoice Ninja', 'udf2' => $this->checkout->payment_hash->hash]; $paymentRequest->currency = $this->checkout->client->getCurrencyCode(); $this->checkout->payment_hash->data = array_merge((array) $this->checkout->payment_hash->data, ['checkout_payment_ref' => $paymentRequest]); diff --git a/app/PaymentDrivers/CheckoutCom/Utilities.php b/app/PaymentDrivers/CheckoutCom/Utilities.php index 764eb224c07e..ef37688c305a 100644 --- a/app/PaymentDrivers/CheckoutCom/Utilities.php +++ b/app/PaymentDrivers/CheckoutCom/Utilities.php @@ -12,12 +12,13 @@ namespace App\PaymentDrivers\CheckoutCom; -use App\Exceptions\PaymentFailed; -use App\Jobs\Util\SystemLogger; -use App\Models\GatewayType; -use App\Models\SystemLog; -use Exception; use stdClass; +use Exception; +use App\Models\SystemLog; +use App\Models\GatewayType; +use App\Jobs\Util\SystemLogger; +use App\Exceptions\PaymentFailed; +use Checkout\Payments\PaymentType; trait Utilities { @@ -60,7 +61,7 @@ trait Utilities $data = [ 'payment_method' => $_payment['source']['id'], - 'payment_type' => 12, + 'payment_type' => \App\Models\PaymentType::CREDIT_CARD_OTHER, 'amount' => $this->getParent()->payment_hash->data->raw_value, 'transaction_reference' => $_payment['id'], 'gateway_type_id' => GatewayType::CREDIT_CARD, diff --git a/app/PaymentDrivers/CheckoutCom/Webhook.php b/app/PaymentDrivers/CheckoutCom/Webhook.php new file mode 100644 index 000000000000..187d2dad2220 --- /dev/null +++ b/app/PaymentDrivers/CheckoutCom/Webhook.php @@ -0,0 +1,91 @@ +checkout = $checkout; + } + + /** + * Lists all possible events in checkout and a brief description + * + * @return void + */ + public function getEventTypes() + { + try { + $response = $this->checkout->gateway->getWorkflowsClient()->getEventTypes(); + + return $response; + + } catch (CheckoutApiException $e) { + // API error + $error_details = $e->error_details; + nlog($error_details); + + $http_status_code = isset($e->http_metadata) ? $e->http_metadata->getStatusCode() : null; + } catch (CheckoutAuthorizationException $e) { + // Bad Invalid authorization + } + + } + + /** + * Lists the workflows in Checkout + * + * @return void + */ + public function getWorkFlows() + { + + try { + + $response = $this->checkout->gateway->getWorkflowsClient()->getWorkflows(); + + return $response; + + } catch (CheckoutApiException $e) { + // API error + $error_details = $e->error_details; + $http_status_code = isset($e->http_metadata) ? $e->http_metadata->getStatusCode() : null; + } catch (CheckoutAuthorizationException $e) { + // Bad Invalid authorization + } + + } + +} \ No newline at end of file diff --git a/app/PaymentDrivers/CheckoutComPaymentDriver.php b/app/PaymentDrivers/CheckoutComPaymentDriver.php index 77b258661c39..6306ec230fa5 100644 --- a/app/PaymentDrivers/CheckoutComPaymentDriver.php +++ b/app/PaymentDrivers/CheckoutComPaymentDriver.php @@ -27,6 +27,7 @@ use App\Models\PaymentType; use App\Models\SystemLog; use App\PaymentDrivers\CheckoutCom\CreditCard; use App\PaymentDrivers\CheckoutCom\Utilities; +use App\PaymentDrivers\CheckoutCom\CheckoutWebhook; use App\Utils\Traits\SystemLogTrait; use Checkout\CheckoutApi; use Checkout\CheckoutApiException; @@ -334,7 +335,7 @@ class CheckoutComPaymentDriver extends BaseDriver $paymentRequest->amount = $this->convertToCheckoutAmount($amount, $this->client->getCurrencyCode()); $paymentRequest->reference = '#'.$invoice->number.' - '.now(); $paymentRequest->customer = $this->getCustomer(); - $paymentRequest->metadata = ['udf1' => 'Invoice Ninja']; + $paymentRequest->metadata = ['udf1' => 'Invoice Ninja', 'udf2' => $payment_hash->hash]; $paymentRequest->currency = $this->client->getCurrencyCode(); $request = new PaymentResponseRequest(); @@ -421,7 +422,19 @@ class CheckoutComPaymentDriver extends BaseDriver public function processWebhookRequest(PaymentWebhookRequest $request) { - return true; + + header('Content-Type: text/plain'); + $webhook_payload = file_get_contents('php://input'); + + if($request->header('cko-signature') == hash_hmac('sha256', $webhook_payload, $this->company_gateway->company->company_key)) { + CheckoutWebhook::dispatch($request->all(), $request->company_key, $this->company_gateway->id)->delay(10); + } + else { + nlog("Hash Mismatch = {$request->header('cko-signature')} ".hash_hmac('sha256', $webhook_payload, $this->company_gateway->company->company_key)); + nlog($request->all()); + } + + return response()->json(['success' => true]); } public function process3dsConfirmation(Checkout3dsRequest $request) diff --git a/app/Repositories/BaseRepository.php b/app/Repositories/BaseRepository.php index 0350cb00b95a..15c93b5fe556 100644 --- a/app/Repositories/BaseRepository.php +++ b/app/Repositories/BaseRepository.php @@ -180,8 +180,6 @@ class BaseRepository unset($tmp_data['client_contacts']); } - nlog($tmp_data); - $model->fill($tmp_data); $model->custom_surcharge_tax1 = $client->company->custom_surcharge_taxes1; diff --git a/config/ninja.php b/config/ninja.php index cddb4bd32ea7..aa22beb2a957 100644 --- a/config/ninja.php +++ b/config/ninja.php @@ -15,8 +15,8 @@ return [ 'require_https' => env('REQUIRE_HTTPS', true), 'app_url' => rtrim(env('APP_URL', ''), '/'), 'app_domain' => env('APP_DOMAIN', 'invoicing.co'), - 'app_version' => env('APP_VERSION','5.6.19'), - 'app_tag' => env('APP_TAG','5.6.19'), + 'app_version' => env('APP_VERSION','5.6.20'), + 'app_tag' => env('APP_TAG','5.6.20'), 'minimum_client_version' => '5.0.16', 'terms_version' => '1.0.1', 'api_secret' => env('API_SECRET', ''),