ldc-crm/app/Http/Controllers/Api/V1/ArcadiaController.php
Jérémy Munsch 81aabdab72
Version 3.0.0
This removes api authentification with token in favor of signature method.
Refactorize code and clean up. It add more unit test plus code coverage
in test/converage.

- Added config.api.timestampTimeout
- Added Helper::signRequest This calculates the request signature
- Added more unit tests and extend code coverage.
- Added rate limiter for web routes
- Added RemoveSpaces Sanitizer on api requests
- Added stress test suite
- Changed AuthServiceProvider api-token to api-hmac
- Changed CustomerSeeder to have at least of customer activated (for unit tests)
- Changed used transation for saveing multiple models
- Changed, in ServiceController generateApiToken to generateServicePassword
- Changed, timestamp has to be send in request with a DateTime format, it also has to a UTC  value
- Composer added brianium/paratest
- Composer update
- Fixed case where removing single optin could be the wrong one (not for the good service)
- Fixed customer delete
- Fixed customer would not be resolved by brand.
- Fixed Really check that request is json checking body content, preventing sending malformed requests
- Remove ApiError::needAuth
- Removed api_token
- Removed ApiController::authenticate
- Removed config.api.tokenTimeout
- Removed Header check or usage because of HAProxy doing weird stuff
- Removed lock files from gitignore
- Removed memcached dependency
- Removed unused questions resources api routes
- Removed workspace file

Migrations
- Added unique index on services.bearer_token
- Changed customer_id on customer_answer is nullable
- Changed services.bearer_pwd length to 312 (the size of the token)
- Dropped services.api_token and service.api_token_date
2022-01-06 12:11:05 +01:00

363 lines
14 KiB
PHP

<?php
namespace App\Http\Controllers\Api\V1;
use App\Http\Controllers\Controller;
use App\Http\Resources\StatusResource;
use App\Mail\CourrierArcadiaError;
use App\Models\Customer;
use DOMDocument;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\Validator;
/**
* @codeCoverageIgnore
*/
class ArcadiaController extends Controller
{
/**
* @param Request $request
* @return StatusResource
*/
public function outgoingMail(Request $request)
{
$validator = Validator::make($request->all(), [
'crm_id' => 'required',
'fields' => 'required|json',
'type' => 'required',
'shipping' => 'required_if:type,default|json'
]);
if ($validator->fails()) {
return new StatusResource([
'status' => 'KO',
'code' => 201,
'message' => 'Error on required fields.',
'errors' => $validator->errors()
]);
}
$brandId = $request->brand_id;
$crmId = $request->crm_id;
$fields = json_decode($request->fields);
$type = $request->type;
$shipping = $type === 'default' ? json_decode($request->shipping) : false;
$customer = Customer::where('id', $crmId)->first();
if (!$customer) {
return new StatusResource(['status' => 'KO', 'code' => 202, 'message' => 'User not found.']);
}
$CustomerInfo = $customer->infos()->where('brand_id', $brandId)->first();
if (!$CustomerInfo) {
return new StatusResource(['status' => 'KO', 'code' => 202, 'message' => 'User not found.']);
}
$check = $this->checkRequiredFields($fields, $type);
if ($check['status'] !== 'OK') {
return new StatusResource($check);
}
if ($type === 'default') {
$check = $this->checkShippingRequiredFields($shipping);
if ($check['status'] !== 'OK') {
return new StatusResource($check);
}
}
$xml = $this->makeXml($fields, $type, $customer, $CustomerInfo, $shipping);
try {
Storage::disk('arcadia')->put(date("Ymd_His") . microtime(true) . ".xml", $xml);
return new StatusResource(['status' => 'OK']);
} catch (\Exception $e) {
Log::critical("ARCADIA XML CREATION FAILED : " . $e->getMessage());
Mail::to(config('mail.MAIL_ADMIN'))->send(new CourrierArcadiaError(
"ARCADIA XML CREATION FAILED",
"Une erreur est survenue lors de la création du fichier XML : " . $e->getMessage(),
$xml ?: ''
));
return new StatusResource(['status' => 'KO', 'code' => 204, 'message' => 'Arcadia XML Creation failed.']);
}
}
/**
* @param mixed $fields
* @return array
*/
private function checkShippingRequiredFields($fields)
{
$test = [
'firstname',
'lastname',
'address',
'city',
'postcode'
];
foreach ($test as $t) {
if (!array_key_exists($t, $fields)) {
$errors[] = ['field' => $t, 'error' => 'required'];
}
}
if (!empty($errors)) {
return ['status' => 'KO', 'code' => 203, 'message' => $errors];
}
return ['status' => 'OK'];
}
/**
*
* @param mixed $fields
* @param mixed $type
* @return array
*/
// phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh, Squiz.Commenting.FunctionComment.Missing
private function checkRequiredFields($fields, $type): array
{
$errors = [];
$key = array_search('operation', array_column($fields->root, 'fieldName'));
if ($key === false) {
$errors[] = ['field' => 'operation', 'error' => 'required'];
}
$key = array_search('support_code', array_column($fields->orderLine, 'fieldName'));
if ($key === false) {
$errors[] = ['field' => 'support_code', 'error' => 'required'];
}
$key = array_search('label_type_support', array_column($fields->orderLine, 'fieldName'));
if ($key === false) {
$errors[] = ['field' => 'label_type_support', 'error' => 'required'];
}
$key = array_search('order_date', array_column($fields->root, 'fieldName'));
if ($key === false) {
$errors[] = ['field' => 'order_date', 'error' => 'required'];
}
$regex = join('', [
'/^(((0[1-9]|[12]\d|3[01])\/(0[13578]|1[02])\/((19|[2-9]\d)\d{2}))|',
'((0[1-9]|[12]\d|30)\/(0[13456789]|1[012])\/((19|[2-9]\d)\d{2}))',
'|((0[1-9]|1\d|2[0-8])\/02\/((19|[2-9]\d)\d{2}))|(29\/02\/((1[6-9]|[2-9]\d)(0[48]',
'|[2468][048]|[13579][26])|((16|[2468][048]|[3579][26])00))))$/'
]);
if (!preg_match($regex, $fields->root[$key]->fieldValue)) {
$errors[] = ['field' => 'order_date', 'error' => 'format must be DD/MM/YYYY'];
}
if ($type === 'default') {
$key = array_search('order_num', array_column($fields->root, 'fieldName'));
if ($key === false) {
$errors[] = ['field' => 'order_num', 'error' => 'required for default type'];
}
if (!is_numeric($fields->root[$key]->fieldValue) || strlen($fields->root[$key]->fieldValue) !== 9) {
$errors[] = ['field' => 'order_num', 'error' => 'format must be 9 numeric characters'];
}
$key = array_search('state_order_cb', array_column($fields->root, 'fieldName'));
if ($key === false) {
$errors[] = ['field' => 'state_order_cb', 'error' => 'required for default type'];
}
if ($fields->root[$key]->fieldValue !== 1 && $fields->root[$key]->fieldValue !== 'Null') {
$errors[] = ['field' => 'state_order_cb', 'error' => 'value must be 1 or Null (Yes Null not NULL)'];
}
$orderCB = $fields->root[$key]->fieldValue;
$key = array_search('state_order_check', array_column($fields->root, 'fieldName'));
if ($key === false) {
$errors[] = ['field' => 'state_order_check', 'error' => 'required for default type'];
}
if ($fields->root[$key]->fieldValue !== 1 && $fields->root[$key]->fieldValue !== 'Null') {
$errors[] = ['field' => 'state_order_check', 'error' => 'value must be 1 or Null (Yes Null not NULL)'];
}
$orderCheck = $fields->root[$key]->fieldValue;
if ($orderCB === $orderCheck) {
$errors[] = [
'field' => 'state_order',
'error' => 'state_order_cb and state_order_check values must be different'
];
}
$key = array_search('package_unit_weight', array_column($fields->orderLine, 'fieldName'));
if ($key === false) {
$errors[] = ['field' => 'package_unit_weight', 'error' => 'required for default type'];
}
} elseif ($type === 'game') {
$key = array_search('code_unique', array_column($fields->orderLine, 'fieldName'));
if ($key === false) {
$errors[] = ['field' => 'code_unique', 'error' => 'required for game type'];
}
}//end if
if (!empty($errors)) {
return ['status' => 'KO', 'code' => 203, 'message' => $errors];
}
return ['status' => 'OK'];
}
/**
* @param mixed $fields
* @param mixed $type
* @param mixed $customer
* @param mixed $customerInfos
* @param mixed $shipping
* @return string|false
*/
// phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh, Squiz.Commenting.FunctionComment.Missing
private function makeXml($fields, $type, $customer, $customerInfos, $shipping)
{
$pivot = [
'order_num' => 'Num_Commande',
// Required if type = default - format 9 numeric characters.
'order_date' => 'Date_Commande',
// Required - format : DD/MM/YYYY .
'operation' => 'Operation',
// Required.
'support_code' => 'Code_Support',
// Required.
'label_type_support' => 'Libelle_Type_Support',
// Required.
'package_unit_weight' => 'Poids_Unitaire_Colis',
// Required if type = default.
'qty' => 'Quantite',
'code_on_pack' => 'Code_on_Pack',
'code_unique' => 'Code_Unique',
// Required if type = game.
// Required if type = default - format 1 ou Null (oui Null et pas NULL).
'state_order_cb' => 'Etat_Commande_CB',
// Required if type = default - format 1 ou Null (oui Null et pas NULL).
'state_order_check' => 'Etat_Commande_Cheque',
];
$dom = new DOMDocument('1.0', 'UTF-8');
$dom->formatOutput = true;
$root = $dom->createElement('export_arcadia');
$dom->appendChild($root);
$order = $root->appendChild($dom->createElement('Commande'));
$key = array_search('order_num', array_column($fields->root, 'fieldName'));
if ($key === false) {
$order->appendChild($dom->createElement('Num_Commande'));
} else {
$order->appendChild($dom->createElement('Num_Commande', $fields->root[$key]->fieldValue));
}
foreach ($fields->root as $value) {
// phpcs:ignore PSR2.ControlStructures.ControlStructureSpacing.SpacingAfterOpenBrace
if (
isset($pivot[$value->fieldName]) && $value->fieldName !== 'state_order_cb' &&
$value->fieldName !== 'state_order_check' && $value->fieldName !== 'order_num'
) {
$order->appendChild($dom->createElement($pivot[$value->fieldName], $value->fieldValue));
}
}
if ($type === 'default') {
$key = array_search('state_order_cb', array_column($fields->root, 'fieldName'));
if ($key) {
$order->appendChild($dom->createElement('Etat_Commande_CB', $fields->root[$key]->fieldValue));
}
$key = array_search('state_order_check', array_column($fields->root, 'fieldName'));
if ($key) {
$order->appendChild($dom->createElement('Etat_Commande_Cheque', $fields->root[$key]->fieldValue));
}
}
$order->appendChild($dom->createElement('Email_conso'))
->appendChild($dom->createCDATASection("$customer->email"));
$order->appendChild($dom->createElement('Civilite'));
if ($type === 'default') {
$order->appendChild($dom->createElement('Nom'))
->appendChild($dom->createCDATASection("$shipping->lastname"));
$order->appendChild($dom->createElement('Prenom'))
->appendChild($dom->createCDATASection("$shipping->firstname"));
$order->appendChild($dom->createElement('Adresse'))
->appendChild($dom->createCDATASection("$shipping->address"));
$order->appendChild($dom->createElement('Adresse_Complementaire'));
$order->appendChild($dom->createElement('CP'))
->appendChild($dom->createCDATASection("$shipping->postcode"));
$order->appendChild($dom->createElement('Ville'))->appendChild($dom->createCDATASection("$shipping->city"));
if (isset($shipping->phone) && !empty($shipping->phone)) {
$order->appendChild($dom->createElement('Telephone'))
->appendChild($dom->createCDATASection("$shipping->phone"));
} else {
$order->appendChild($dom->createElement('Telephone'));
}
} else {
$order->appendChild($dom->createElement('Nom'))
->appendChild($dom->createCDATASection("$customerInfos->lastname"));
$order->appendChild($dom->createElement('Prenom'))
->appendChild($dom->createCDATASection("$customerInfos->firstname"));
$order->appendChild($dom->createElement('Adresse'))
->appendChild($dom->createCDATASection("$customerInfos->address"));
$order->appendChild($dom->createElement('Adresse_Complementaire'))
->appendChild($dom->createCDATASection("$customerInfos->address2"));
$order->appendChild($dom->createElement('CP'))
->appendChild($dom->createCDATASection("$customerInfos->postcode"));
$order->appendChild($dom->createElement('Ville'))
->appendChild($dom->createCDATASection("$customerInfos->city"));
$order->appendChild($dom->createElement('Telephone'))
->appendChild($dom->createCDATASection("$customerInfos->phone"));
}//end if
$orderLine = $order->appendChild($dom->createElement('Ligne_de_Commande'));
$key = array_search('qty', array_column($fields->orderLine, 'fieldName'));
if ($key === false) {
$orderLine->appendChild($dom->createElement('Quantite', '1'));
}
$key = array_search('package_unit_weight', array_column($fields->orderLine, 'fieldName'));
if ($key === false) {
$orderLine->appendChild($dom->createElement('Poids_Unitaire_Colis'));
}
foreach ($fields->orderLine as $value) {
// phpcs:ignore PSR2.ControlStructures.ControlStructureSpacing.SpacingAfterOpenBrace
if (
isset($pivot[$value->fieldName]) &&
$value->fieldName !== 'code_on_pack' &&
$value->fieldName !== 'code_unique'
) {
$orderLine->appendChild($dom->createElement($pivot[$value->fieldName], $value->fieldValue));
}
}
if ($type === 'game') {
$key = array_search('code_on_pack', array_column($fields->orderLine, 'fieldName'));
if ($key) {
$orderLine->appendChild(
$dom->createElement('Code_on_Pack')
)->appendChild($dom->createCDATASection($fields->orderLine[$key]->fieldValue));
}
$key = array_search('code_unique', array_column($fields->orderLine, 'fieldName'));
if ($key) {
$orderLine->appendChild(
$dom->createElement('Code_Unique')
)->appendChild($dom->createCDATASection($fields->orderLine[$key]->fieldValue));
}
}
return $dom->saveXML();
}
}