Magento 2 Documentation  2.3
Documentation for Magento 2 CMS v2.3 (December 2018)
Settlement.php
Go to the documentation of this file.
1 <?php
8 
9 use DateTime;
12 
30 {
34  const REPORTS_HOSTNAME = "reports.paypal.com";
35 
39  const SANDBOX_REPORTS_HOSTNAME = "reports.sandbox.paypal.com";
40 
44  const REPORTS_PATH = "/ppreports/outgoing";
45 
49  const FILES_IN_CHARSET = "UTF-16";
50 
54  const FILES_OUT_CHARSET = "UTF-8";
55 
61  protected $_rows = [];
62 
66  protected $_csvColumns = [
67  'old' => [
68  'section_columns' => [
69  '' => 0,
70  'TransactionID' => 1,
71  'InvoiceID' => 2,
72  'PayPalReferenceID' => 3,
73  'PayPalReferenceIDType' => 4,
74  'TransactionEventCode' => 5,
75  'TransactionInitiationDate' => 6,
76  'TransactionCompletionDate' => 7,
77  'TransactionDebitOrCredit' => 8,
78  'GrossTransactionAmount' => 9,
79  'GrossTransactionCurrency' => 10,
80  'FeeDebitOrCredit' => 11,
81  'FeeAmount' => 12,
82  'FeeCurrency' => 13,
83  'CustomField' => 14,
84  'ConsumerID' => 15,
85  ],
86  'rowmap' => [
87  'TransactionID' => 'transaction_id',
88  'InvoiceID' => 'invoice_id',
89  'PayPalReferenceID' => 'paypal_reference_id',
90  'PayPalReferenceIDType' => 'paypal_reference_id_type',
91  'TransactionEventCode' => 'transaction_event_code',
92  'TransactionInitiationDate' => 'transaction_initiation_date',
93  'TransactionCompletionDate' => 'transaction_completion_date',
94  'TransactionDebitOrCredit' => 'transaction_debit_or_credit',
95  'GrossTransactionAmount' => 'gross_transaction_amount',
96  'GrossTransactionCurrency' => 'gross_transaction_currency',
97  'FeeDebitOrCredit' => 'fee_debit_or_credit',
98  'FeeAmount' => 'fee_amount',
99  'FeeCurrency' => 'fee_currency',
100  'CustomField' => 'custom_field',
101  'ConsumerID' => 'consumer_id',
102  ],
103  ],
104  'new' => [
105  'section_columns' => [
106  '' => 0,
107  'Transaction ID' => 1,
108  'Invoice ID' => 2,
109  'PayPal Reference ID' => 3,
110  'PayPal Reference ID Type' => 4,
111  'Transaction Event Code' => 5,
112  'Transaction Initiation Date' => 6,
113  'Transaction Completion Date' => 7,
114  'Transaction Debit or Credit' => 8,
115  'Gross Transaction Amount' => 9,
116  'Gross Transaction Currency' => 10,
117  'Fee Debit or Credit' => 11,
118  'Fee Amount' => 12,
119  'Fee Currency' => 13,
120  'Custom Field' => 14,
121  'Consumer ID' => 15,
122  'Payment Tracking ID' => 16,
123  'Store ID' => 17,
124  ],
125  'rowmap' => [
126  'Transaction ID' => 'transaction_id',
127  'Invoice ID' => 'invoice_id',
128  'PayPal Reference ID' => 'paypal_reference_id',
129  'PayPal Reference ID Type' => 'paypal_reference_id_type',
130  'Transaction Event Code' => 'transaction_event_code',
131  'Transaction Initiation Date' => 'transaction_initiation_date',
132  'Transaction Completion Date' => 'transaction_completion_date',
133  'Transaction Debit or Credit' => 'transaction_debit_or_credit',
134  'Gross Transaction Amount' => 'gross_transaction_amount',
135  'Gross Transaction Currency' => 'gross_transaction_currency',
136  'Fee Debit or Credit' => 'fee_debit_or_credit',
137  'Fee Amount' => 'fee_amount',
138  'Fee Currency' => 'fee_currency',
139  'Custom Field' => 'custom_field',
140  'Consumer ID' => 'consumer_id',
141  'Payment Tracking ID' => 'payment_tracking_id',
142  'Store ID' => 'store_id',
143  ],
144  ],
145  ];
146 
150  protected $_tmpDirectory;
151 
155  protected $_storeManager;
156 
160  protected $_scopeConfig;
161 
167  private $dateTimeColumns = ['transaction_initiation_date', 'transaction_completion_date'];
168 
174  private $amountColumns = ['gross_transaction_amount', 'fee_amount'];
175 
179  private $serializer;
180 
192  public function __construct(
193  \Magento\Framework\Model\Context $context,
194  \Magento\Framework\Registry $registry,
195  \Magento\Framework\Filesystem $filesystem,
197  \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig,
198  \Magento\Framework\Model\ResourceModel\AbstractResource $resource = null,
199  \Magento\Framework\Data\Collection\AbstractDb $resourceCollection = null,
200  array $data = [],
201  \Magento\Framework\Serialize\Serializer\Json $serializer = null
202  ) {
203  $this->_tmpDirectory = $filesystem->getDirectoryWrite(DirectoryList::SYS_TMP);
204  $this->_storeManager = $storeManager;
205  $this->_scopeConfig = $scopeConfig;
206  parent::__construct($context, $registry, $resource, $resourceCollection, $data);
207  $this->serializer = $serializer ?: \Magento\Framework\App\ObjectManager::getInstance()
208  ->get(\Magento\Framework\Serialize\Serializer\Json::class);
209  }
210 
216  protected function _construct()
217  {
218  $this->_init(\Magento\Paypal\Model\ResourceModel\Report\Settlement::class);
219  }
220 
226  public function beforeSave()
227  {
228  $this->_dataSaveAllowed = true;
229  if ($this->getId()) {
230  if ($this->getLastModified() == $this->getReportLastModified()) {
231  $this->_dataSaveAllowed = false;
232  }
233  }
234  $this->setLastModified($this->getReportLastModified());
235  return parent::beforeSave();
236  }
237 
246  public function fetchAndSave(\Magento\Framework\Filesystem\Io\Sftp $connection)
247  {
248  $fetched = 0;
249  $listing = $this->_filterReportsList($connection->rawls());
250  foreach ($listing as $filename => $attributes) {
251  $localCsv = 'PayPal_STL_' . uniqid(\Magento\Framework\Math\Random::getRandomNumber()) . time() . '.csv';
252  if ($connection->read($filename, $this->_tmpDirectory->getAbsolutePath($localCsv))) {
253  if (!$this->_tmpDirectory->isWritable($localCsv)) {
254  throw new \Magento\Framework\Exception\LocalizedException(
255  __('We cannot create a target file for reading reports.')
256  );
257  }
258 
259  $encoded = $this->_tmpDirectory->readFile($localCsv);
260  $csvFormat = 'new';
261 
262  $fileEncoding = mb_detect_encoding($encoded);
263 
264  if (self::FILES_OUT_CHARSET != $fileEncoding) {
265  $decoded = @iconv($fileEncoding, self::FILES_OUT_CHARSET . '//IGNORE', $encoded);
266  $this->_tmpDirectory->writeFile($localCsv, $decoded);
267  $csvFormat = 'old';
268  }
269 
270  // Set last modified date, this value will be overwritten during parsing
271  if (isset($attributes['mtime'])) {
272  $date = new \DateTime();
273  $lastModified = $date->setTimestamp($attributes['mtime']);
274  $this->setReportLastModified(
275  $lastModified->format('Y-m-d H:i:s')
276  );
277  }
278 
279  $this->setReportDate(
280  $this->_fileNameToDate($filename)
281  )->setFilename(
282  $filename
283  )->parseCsv(
284  $localCsv,
285  $csvFormat
286  );
287 
288  if ($this->getAccountId()) {
289  $this->save();
290  }
291 
292  if ($this->_dataSaveAllowed) {
293  $fetched += count($this->_rows);
294  }
295  // clean object and remove parsed file
296  $this->unsetData();
297  $this->_tmpDirectory->delete($localCsv);
298  }
299  }
300  return $fetched;
301  }
302 
310  public static function createConnection(array $config)
311  {
312  if (!isset($config['hostname'])
313  || !isset($config['username'])
314  || !isset($config['password'])
315  || !isset($config['path'])
316  ) {
317  throw new \InvalidArgumentException('Required config elements: hostname, username, password, path');
318  }
319  $connection = new \Magento\Framework\Filesystem\Io\Sftp();
320  $connection->open(
321  ['host' => $config['hostname'], 'username' => $config['username'], 'password' => $config['password']]
322  );
323  $connection->cd($config['path']);
324  return $connection;
325  }
326 
335  public function parseCsv($localCsv, $format = 'new')
336  {
337  $this->_rows = [];
338 
339  $sectionColumns = $this->_csvColumns[$format]['section_columns'];
340  $rowMap = $this->_csvColumns[$format]['rowmap'];
341 
342  $flippedSectionColumns = array_flip($sectionColumns);
343  $stream = $this->_tmpDirectory->openFile($localCsv, 'r');
344  while ($line = $stream->readCsv()) {
345  if (empty($line)) {
346  // The line was empty, so skip it.
347  continue;
348  }
349  $lineType = $line[0];
350  switch ($lineType) {
351  case 'RH':
352  // Report header.
353  $lastModified = new \DateTime($line[1]);
354  $this->setReportLastModified(
355  $lastModified->format('Y-m-d H:i:s')
356  );
357  //$this->setAccountId($columns[2]); -- probably we'll just take that from the section header...
358  break;
359  case 'FH':
360  // File header.
361  // Nothing interesting here, move along
362  break;
363  case 'SH':
364  // Section header.
365  $this->setAccountId($line[3]);
366  $this->loadByAccountAndDate();
367  break;
368  case 'CH':
369  // Section columns.
370  // In case ever the column order is changed, we will have the items recorded properly
371  // anyway. We have named, not numbered columns.
372  $count = count($line);
373  for ($i = 1; $i < $count; $i++) {
374  $sectionColumns[$line[$i]] = $i;
375  }
376  $flippedSectionColumns = array_flip($sectionColumns);
377  break;
378  case 'SB':
379  // Section body.
380  $this->_rows[] = $this->getBodyItems($line, $flippedSectionColumns, $rowMap);
381  break;
382  case 'SC':
383  // Section records count.
384  case 'RC':
385  // Report records count.
386  case 'SF':
387  // Section footer.
388  case 'FF':
389  // File footer.
390  case 'RF':
391  // Report footer.
392  // Nothing to see here, move along
393  break;
394  default:
395  break;
396  }
397  }
398  return $this;
399  }
400 
409  private function getBodyItems(array $line, array $sectionColumns, array $rowMap)
410  {
411  $bodyItem = [];
412  for ($i = 1, $count = count($line); $i < $count; $i++) {
413  if (isset($rowMap[$sectionColumns[$i]])) {
414  if (in_array($rowMap[$sectionColumns[$i]], $this->dateTimeColumns)) {
415  $line[$i] = $this->formatDateTimeColumns($line[$i]);
416  }
417  if (in_array($rowMap[$sectionColumns[$i]], $this->amountColumns)) {
418  $line[$i] = $this->formatAmountColumn($line[$i]);
419  }
420  $bodyItem[$rowMap[$sectionColumns[$i]]] = $line[$i];
421  }
422  }
423  return $bodyItem;
424  }
425 
432  private function formatDateTimeColumns($lineItem)
433  {
435  $date = new DateTime($lineItem, new \DateTimeZone('UTC'));
436  return $date->format(\Magento\Framework\Stdlib\DateTime::DATETIME_PHP_FORMAT);
437  }
438 
447  private function formatAmountColumn($lineItem)
448  {
449  return intval($lineItem) / 100;
450  }
451 
457  public function loadByAccountAndDate()
458  {
459  $this->getResource()->loadByAccountAndDate($this, $this->getAccountId(), $this->getReportDate());
460  return $this;
461  }
462 
468  public function getRows()
469  {
470  return $this->_rows;
471  }
472 
480  public function getFieldLabel($field)
481  {
482  switch ($field) {
483  case 'report_date':
484  return __('Report Date');
485  case 'account_id':
486  return __('Merchant Account');
487  case 'transaction_id':
488  return __('Transaction ID');
489  case 'invoice_id':
490  return __('Invoice ID');
491  case 'paypal_reference_id':
492  return __('PayPal Reference ID');
493  case 'paypal_reference_id_type':
494  return __('PayPal Reference ID Type');
495  case 'transaction_event_code':
496  return __('Event Code');
497  case 'transaction_event':
498  return __('Event');
499  case 'transaction_initiation_date':
500  return __('Start Date');
501  case 'transaction_completion_date':
502  return __('Finish Date');
503  case 'transaction_debit_or_credit':
504  return __('Debit or Credit');
505  case 'gross_transaction_amount':
506  return __('Gross Amount');
507  case 'fee_debit_or_credit':
508  return __('Fee Debit or Credit');
509  case 'fee_amount':
510  return __('Fee Amount');
511  case 'custom_field':
512  return __('Custom');
513  default:
514  return $field;
515  }
516  }
517 
527  public function getSftpCredentials($automaticMode = false)
528  {
529  $configs = [];
530  $uniques = [];
531  foreach ($this->_storeManager->getStores() as $store) {
532  /*@var $store \Magento\Store\Model\Store */
533  $active = $this->_scopeConfig->isSetFlag(
534  'paypal/fetch_reports/active',
536  $store
537  );
538  if (!$active && $automaticMode) {
539  continue;
540  }
541  $cfg = [
542  'hostname' => $this->_scopeConfig->getValue(
543  'paypal/fetch_reports/ftp_ip',
545  $store
546  ),
547  'path' => $this->_scopeConfig->getValue(
548  'paypal/fetch_reports/ftp_path',
550  $store
551  ),
552  'username' => $this->_scopeConfig->getValue(
553  'paypal/fetch_reports/ftp_login',
555  $store
556  ),
557  'password' => $this->_scopeConfig->getValue(
558  'paypal/fetch_reports/ftp_password',
560  $store
561  ),
562  'sandbox' => $this->_scopeConfig->getValue(
563  'paypal/fetch_reports/ftp_sandbox',
565  $store
566  ),
567  ];
568  if (empty($cfg['username']) || empty($cfg['password'])) {
569  continue;
570  }
571  if (empty($cfg['hostname']) || $cfg['sandbox']) {
572  $cfg['hostname'] = $cfg['sandbox'] ? self::SANDBOX_REPORTS_HOSTNAME : self::REPORTS_HOSTNAME;
573  }
574  if (empty($cfg['path']) || $cfg['sandbox']) {
575  $cfg['path'] = self::REPORTS_PATH;
576  }
577  // avoid duplicates
578  if (in_array($this->serializer->serialize($cfg), $uniques)) {
579  continue;
580  }
581  $uniques[] = $this->serializer->serialize($cfg);
582  $configs[] = $cfg;
583  }
584  return $configs;
585  }
586 
593  protected function _fileNameToDate($filename)
594  {
595  // Currently filenames look like STL-YYYYMMDD, so that is what we care about.
596  $dateSnippet = substr(basename($filename), 4, 8);
597  $result = substr($dateSnippet, 0, 4) . '-' . substr($dateSnippet, 4, 2) . '-' . substr($dateSnippet, 6, 2);
598  return $result;
599  }
600 
607  protected function _filterReportsList($list)
608  {
609  $result = [];
610  $pattern = '/^STL-(\d{8,8})\.(\d{2,2})\.(.{3,3})\.CSV$/';
611  foreach ($list as $filename => $data) {
612  if (preg_match($pattern, $filename)) {
613  $result[$filename] = $data;
614  }
615  }
616  return $result;
617  }
618 }
__construct(\Magento\Framework\Model\Context $context, \Magento\Framework\Registry $registry, \Magento\Framework\Filesystem $filesystem, \Magento\Store\Model\StoreManagerInterface $storeManager, \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig, \Magento\Framework\Model\ResourceModel\AbstractResource $resource=null, \Magento\Framework\Data\Collection\AbstractDb $resourceCollection=null, array $data=[], \Magento\Framework\Serialize\Serializer\Json $serializer=null)
Definition: Settlement.php:192
$pattern
Definition: website.php:22
$config
Definition: fraud_order.php:17
$count
Definition: recent.phtml:13
$storeManager
__()
Definition: __.php:13
$resource
Definition: bulk.php:12
$format
Definition: list.phtml:12
static createConnection(array $config)
Definition: Settlement.php:310
fetchAndSave(\Magento\Framework\Filesystem\Io\Sftp $connection)
Definition: Settlement.php:246
$attributes
Definition: matrix.phtml:13
getSftpCredentials($automaticMode=false)
Definition: Settlement.php:527
$connection
Definition: bulk.php:13
parseCsv($localCsv, $format='new')
Definition: Settlement.php:335
$filesystem
$i
Definition: gallery.phtml:31