<?php
namespace App\Service;
use App\Api\PrestaShopWebservice;
use App\Entity\Sale;
use App\Repository\SaleRepository;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
class PrestashopApi
{
private $em;
private $sr;
private $geo;
/**
* @var PrestaShopWebservice
*/
private $client;
public function __construct(EntityManagerInterface $em, SaleRepository $sr, Geocoding $geo)
{
$this->em = $em;
$this->sr = $sr;
$this->geo = $geo;
}
public function init()
{
$url = $_ENV['PRESTASHOP_API_URL'];
$key = $_ENV['PRESTASHOP_API_KEY'];
if (!$url || !$key) {
throw new \InvalidArgumentException('No api url or key set in .env');
}
try {
$this->client = new PrestaShopWebservice($url, $key, false);
} catch (\Exception $exception) {
return false;
}
return true;
}
public function getOrders($from_date, int $shop_id = 1)
{
$now = date('Y-m-d H:i:s', time() + 7200);
$opt = [
'resource' => 'orders',
'display' => 'full',
// 'filter[current_state]' => '[3]',
'filter[date_upd]' => '[' . $from_date . ',' . $now . ']',
'date' => '1',
'id_shop' => $shop_id,
];
$ret = $this->client->get($opt);
return $ret->children()->children();
}
public function getOrder($id)
{
$opt = [
'resource' => 'orders',
'display' => 'full',
'id' => $id,
];
$ret = $this->client->get($opt);
return $ret->children()->order;
}
public function getCustomer($id)
{
$opt = [
'resource' => 'customers',
'id' => $id,
];
$ret = $this->client->get($opt);
return $ret->children()->children();
}
public function getMessage($order)
{
try {
$a = $this->client->get([
'resource' => 'customer_threads',
'filter[id_order]' => '[' . (int)$order->id . ']',
]);
if (($thread_id = (int)$a->children()->children()->customer_thread['id']) > 0) {
$b = $this->client->get([
'resource' => 'customer_messages',
'display' => 'full',
'filter[id_customer_thread]' => '[' . $thread_id . ']',
]);
return (string)$b->children()->children()->customer_message->message;
}
} catch (\Exception $e) {
// do nothing
}
return '';
}
private $_address_cache = [];
public function getAddress($id, $email)
{
if (!isset($this->_address_cache[$id])) {
$opt = [
'resource' => 'addresses',
'id' => $id,
];
$a = $this->client->get($opt);
$a = $a->children()->children();
$street = (string)$a->address1;
$number = (string)$a->address2;
if ($number) { // if number is in address2, remove it from street
$street = preg_replace('/,?\s*' . preg_quote($number, '/') . '$/', '', $street);
}
$addr = [
'address_id' => (int)$a->id,
'firstname' => (string)$a->firstname,
'lastname' => (string)$a->lastname,
'email' => $email,
'telephone' => $this->getPhone($a),
'street' => $street,
'number' => $number,
'toevoeging' => '',
'postcode' => (string)$a->postcode,
'city' => (string)$a->city,
'country' => $this->getCountry((int)$a->id_country),
'lat' => null,
'lng' => null,
];
if (($addr['number'] && $addr['postcode'] && ($addr['country'] === 'NL')) || ($addr['street'] && $addr['postcode'] && $addr['city'])) {
[$lat, $lng] = $this->geo->getLatLng($addr['street'], $addr['number'], $addr['postcode'], $addr['city'], $addr['country']);
$addr['lat'] = $lat;
$addr['lng'] = $lng;
}
$this->_address_cache[$id] = $addr;
}
return $this->_address_cache[$id];
}
private function getPhone($a)
{
$phone = [];
(string)$a->phone && $phone[] = (string)$a->phone;
(string)$a->phone_mobile && $phone[] = (string)$a->phone_mobile;
return implode(' / ', $phone);
}
public function getCountry($c)
{
$ret = 'Nederland?';
switch ($c) {
case 3: // België
case 26: // Bëlgie-Antwerpen
case 37: // België-Brussel
case 33: // België-Henegouwen
case 27: // Bëlgie-Limburg
case 32: // België-Luik
case 35: // België-Luxemburg
case 34: // België-Namen
case 28: // België-Oost-Vlaanderen
case 30: // België-Vlaams-Brabant
case 31: // België-Waals-Brabant
case 29: // België-West-Vlaanderen
$ret = 'België';
break;
case 36: // Duitsland-Nordrhein-Westfalen
$ret = 'Duitsland';
break;
case 13: // Nederland
case 18: // Nederland-Brabant
case 14: // Nederland-Drenthe
case 22: // Nederland-Flevoland
case 15: // Nederland-Friesland
case 16: // Nederland-Gelderland
case 25: // Nederland-Groningen
case 24: // Nederland-Limburg
case 19: // Nederland-Noord Holland
case 21: // Nederland-Overijsel
case 17: // Nederland-Utrecht
case 23: // Nederland-Zeeland
case 20: // Nederland-Zuid Holland
$ret = 'Nederland';
break;
}
return $ret;
}
public function sync($from_date = null, int $shop_id = 1, SymfonyStyle $io): void
{
try {
if ($orders = $this->getOrders($from_date, $shop_id)) {
$io->progressStart(\count($orders));
foreach ($orders as $order) {
$sale = $this->sr->get((int)$order->id);
if ($sale->getId()) {
$this->update($order, $sale);
} else {
$this->create($order, $sale);
}
$io->progressAdvance(1);
}
$io->progressFinish();
}
} catch (\Exception $e) {
throw new \Exception('error syncing Prestashop: ' . $e->getMessage());
}
}
public function syncOrder($id, SymfonyStyle $io): void
{
if ($order = $this->getOrder($id)) {
$sale = $this->sr->get((int)$order->id);
if ($sale->getId()) {
$this->update($order, $sale);
$io->success('Sync updated order ' . $id);
} else {
$this->create($order, $sale);
$io->success('Sync imported order ' . $id);
}
} else {
$io->error('Order ' . $id . ' does not exist');
}
}
private function create(\SimpleXMLElement $order, Sale $sale): void
{
$customer = $this->getCustomer((int)$order->id_customer);
$ba = $this->getAddress((int)$order->id_address_invoice, (string)$customer->email);
$sa = $this->getAddress((int)$order->id_address_delivery, (string)$customer->email);
$total_paid = (float)$order->total_paid;
$tax = round((float)$order->total_paid_tax_incl - (float)$order->total_paid_tax_excl, 2);
$info = [
'order_id' => (int)$order->id,
'increment_id' => (int)$order->id,
'subtotal' => (float)$order->total_products,
'shipping' => (float)$order->total_shipping,
'total' => $total_paid,
'tax' => $tax,
'total_paid' => $total_paid,
'total_due' => 0,
'payment_method' => $this->getPaymentMethod($order),
'shipping_method' => $this->getShippingMethod($order),
'description' => $this->getMessage($order),
'address' => [
'billing' => $ba,
'shipping' => $sa
],
'company' => false,
'items' => $this->getItemsFromOrder($order),
];
$sale->setType(Sale::TYPE_PRESTASHOP);
$sale->setStatus($this->getStatus($order));
$sale->setInfo($info);
$sale->setStore(Sale::STORE_VINKBOMEN);
$sale->setPickup($info['shipping'] === 'ophalen');
$sale->setCreatedAt(new \DateTime($order['created_at']));
$this->em->persist($sale);
$this->em->flush();
sleep(1);
}
private function update(\SimpleXMLElement $order, Sale $sale): void
{
try {
$sale->setStatus($this->getStatus($order));
$info = $sale->getInfo();
$info['total_paid'] = (float)$order->total_paid;
// $info['total_due'] = (float)$order['total_due'];
$info['items'] = $this->getItemsFromOrder($order);
$sale->setInfo($info);
$this->em->persist($sale);
$this->em->flush();
} catch (\Exception $e) {
}
}
private function getItemsFromOrder(\SimpleXMLElement $order): array
{
$items = [];
foreach ($order->associations->order_rows->order_row as $item) {
$quantity = (int)$item->product_quantity;
$price = round((float)$item->unit_price_tax_incl, 2);
$items[] = [
'item_id' => (int)$item->id,
'product_id' => (int)$item->product_id,
'sku' => (string)$item->product_reference,
'name' => (string)$item->product_name,
'quantity' => $quantity,
'price' => $price,
'total' => round($price * $quantity, 2),
'tax_invoiced' => null,
'tax_percent' => null,
];
}
return $items;
}
private function getStatus(\SimpleXMLElement $order)
{
/**
* 0 => Awaiting bank wire payment
* 1 => Awaiting Cash On Delivery validation
* 2 => Awaiting check payment
* 3 => Canceled
* 4 => Delivered
* 5 => On backorder (not paid)
* 6 => On backorder (paid)
* 7 => Payment accepted
* 8 => Payment error
* 9 => Processing in progress
* 10 => Refunded
* 11 => Remote payment accepted
* 12 => Shipped
*/
switch ((int)$order->current_state) {
case 1: // => Awaiting check payment
case 10: // => Awaiting bank wire payment
case 11: // => Awaiting Paypal payment
case 14: // => Awaiting cod validation
return Sale::STATUS_PENDING;
case 6: // => Canceled
case 7: // => Refunded
return Sale::STATUS_CANCELLED;
case 8: // => Payment error
case 13: // => On backorder (not paid)
return Sale::STATUS_ON_HOLD;
case 2: // => Payment accepted
case 3: // => Processing in progress
case 9: // => On backorder (paid)
case 12: // => Remote payment accepted
return Sale::STATUS_PROCESSING;
case 4: // => Shipped
case 5: // => Delivered
return Sale::STATUS_CLOSED;
// return Sale::STATUS_COMPLETED;
}
return '?';
}
private function getPaymentMethod(\SimpleXMLElement $order)
{
switch ((string)$order->module) {
case 'cashondelivery':
case 'ps_checkpayment':
return 'cashondelivery';
}
return 'ideal';
// return 'pin';
// return 'ideal';
// return 'cashondelivery';
}
private function getShippingMethod(\SimpleXMLElement $order)
{
switch ((int)$order->id_carrier) {
case 1:
return 'ophalen';
case 2:
return 'leveren';
}
return 'leveren';
}
}