<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="fr">
		<id>https://wiki.siladel.fr/index.php?action=history&amp;feed=atom&amp;title=Stripeaccounting%2Fen</id>
		<title>Stripeaccounting/en - Historique des versions</title>
		<link rel="self" type="application/atom+xml" href="https://wiki.siladel.fr/index.php?action=history&amp;feed=atom&amp;title=Stripeaccounting%2Fen"/>
		<link rel="alternate" type="text/html" href="https://wiki.siladel.fr/index.php?title=Stripeaccounting/en&amp;action=history"/>
		<updated>2026-08-02T22:48:20Z</updated>
		<subtitle>Historique des versions pour cette page sur le wiki</subtitle>
		<generator>MediaWiki 1.30.0</generator>

	<entry>
		<id>https://wiki.siladel.fr/index.php?title=Stripeaccounting/en&amp;diff=553&amp;oldid=prev</id>
		<title>Goldron le 2 août 2026 à 12:41</title>
		<link rel="alternate" type="text/html" href="https://wiki.siladel.fr/index.php?title=Stripeaccounting/en&amp;diff=553&amp;oldid=prev"/>
				<updated>2026-08-02T12:41:00Z</updated>
		
		<summary type="html">&lt;p&gt;&lt;/p&gt;
&lt;table class=&quot;diff diff-contentalign-left&quot; data-mw=&quot;interface&quot;&gt;
				&lt;col class=&quot;diff-marker&quot; /&gt;
				&lt;col class=&quot;diff-content&quot; /&gt;
				&lt;col class=&quot;diff-marker&quot; /&gt;
				&lt;col class=&quot;diff-content&quot; /&gt;
				&lt;tr style=&quot;vertical-align: top;&quot; lang=&quot;fr&quot;&gt;
				&lt;td colspan=&quot;2&quot; style=&quot;background-color: white; color:black; text-align: center;&quot;&gt;← Version précédente&lt;/td&gt;
				&lt;td colspan=&quot;2&quot; style=&quot;background-color: white; color:black; text-align: center;&quot;&gt;Version du 2 août 2026 à 12:41&lt;/td&gt;
				&lt;/tr&gt;&lt;tr&gt;&lt;td colspan=&quot;2&quot; class=&quot;diff-lineno&quot; id=&quot;mw-diff-left-l1&quot; &gt;Ligne 1 :&lt;/td&gt;
&lt;td colspan=&quot;2&quot; class=&quot;diff-lineno&quot;&gt;Ligne 1 :&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class='diff-marker'&gt;&amp;#160;&lt;/td&gt;&lt;td style=&quot;background-color: #f9f9f9; color: #333333; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #e6e6e6; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;{{DISPLAYTITLE:StripeAccounting — Technical documentation}}&lt;/div&gt;&lt;/td&gt;&lt;td class='diff-marker'&gt;&amp;#160;&lt;/td&gt;&lt;td style=&quot;background-color: #f9f9f9; color: #333333; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #e6e6e6; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;{{DISPLAYTITLE:StripeAccounting — Technical documentation}}&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class='diff-marker'&gt;&amp;#160;&lt;/td&gt;&lt;td style=&quot;background-color: #f9f9f9; color: #333333; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #e6e6e6; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;/td&gt;&lt;td class='diff-marker'&gt;&amp;#160;&lt;/td&gt;&lt;td style=&quot;background-color: #f9f9f9; color: #333333; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #e6e6e6; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class='diff-marker'&gt;−&lt;/td&gt;&lt;td style=&quot;color:black; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #ffe49c; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;[[Fichier:Fr.png|link=Stripeaccounting]]&lt;/div&gt;&lt;/td&gt;&lt;td class='diff-marker'&gt;+&lt;/td&gt;&lt;td style=&quot;color:black; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #a3d3ff; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;[[Fichier:Fr.png&lt;ins class=&quot;diffchange diffchange-inline&quot;&gt;|32px&lt;/ins&gt;|link=Stripeaccounting&lt;ins class=&quot;diffchange diffchange-inline&quot;&gt;|Doc en Français&lt;/ins&gt;]]&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class='diff-marker'&gt;&amp;#160;&lt;/td&gt;&lt;td style=&quot;background-color: #f9f9f9; color: #333333; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #e6e6e6; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;/td&gt;&lt;td class='diff-marker'&gt;&amp;#160;&lt;/td&gt;&lt;td style=&quot;background-color: #f9f9f9; color: #333333; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #e6e6e6; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class='diff-marker'&gt;&amp;#160;&lt;/td&gt;&lt;td style=&quot;background-color: #f9f9f9; color: #333333; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #e6e6e6; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;/td&gt;&lt;td class='diff-marker'&gt;&amp;#160;&lt;/td&gt;&lt;td style=&quot;background-color: #f9f9f9; color: #333333; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #e6e6e6; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/table&gt;</summary>
		<author><name>Goldron</name></author>	</entry>

	<entry>
		<id>https://wiki.siladel.fr/index.php?title=Stripeaccounting/en&amp;diff=550&amp;oldid=prev</id>
		<title>Goldron le 2 août 2026 à 12:36</title>
		<link rel="alternate" type="text/html" href="https://wiki.siladel.fr/index.php?title=Stripeaccounting/en&amp;diff=550&amp;oldid=prev"/>
				<updated>2026-08-02T12:36:37Z</updated>
		
		<summary type="html">&lt;p&gt;&lt;/p&gt;
&lt;table class=&quot;diff diff-contentalign-left&quot; data-mw=&quot;interface&quot;&gt;
				&lt;col class=&quot;diff-marker&quot; /&gt;
				&lt;col class=&quot;diff-content&quot; /&gt;
				&lt;col class=&quot;diff-marker&quot; /&gt;
				&lt;col class=&quot;diff-content&quot; /&gt;
				&lt;tr style=&quot;vertical-align: top;&quot; lang=&quot;fr&quot;&gt;
				&lt;td colspan=&quot;2&quot; style=&quot;background-color: white; color:black; text-align: center;&quot;&gt;← Version précédente&lt;/td&gt;
				&lt;td colspan=&quot;2&quot; style=&quot;background-color: white; color:black; text-align: center;&quot;&gt;Version du 2 août 2026 à 12:36&lt;/td&gt;
				&lt;/tr&gt;&lt;tr&gt;&lt;td colspan=&quot;2&quot; class=&quot;diff-lineno&quot; id=&quot;mw-diff-left-l1&quot; &gt;Ligne 1 :&lt;/td&gt;
&lt;td colspan=&quot;2&quot; class=&quot;diff-lineno&quot;&gt;Ligne 1 :&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class='diff-marker'&gt;&amp;#160;&lt;/td&gt;&lt;td style=&quot;background-color: #f9f9f9; color: #333333; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #e6e6e6; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;{{DISPLAYTITLE:StripeAccounting — Technical documentation}}&lt;/div&gt;&lt;/td&gt;&lt;td class='diff-marker'&gt;&amp;#160;&lt;/td&gt;&lt;td style=&quot;background-color: #f9f9f9; color: #333333; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #e6e6e6; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;{{DISPLAYTITLE:StripeAccounting — Technical documentation}}&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class='diff-marker'&gt;&amp;#160;&lt;/td&gt;&lt;td style=&quot;background-color: #f9f9f9; color: #333333; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #e6e6e6; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;/td&gt;&lt;td class='diff-marker'&gt;&amp;#160;&lt;/td&gt;&lt;td style=&quot;background-color: #f9f9f9; color: #333333; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #e6e6e6; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class='diff-marker'&gt;−&lt;/td&gt;&lt;td style=&quot;color:black; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #ffe49c; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;[[Stripeaccounting]]&lt;/div&gt;&lt;/td&gt;&lt;td class='diff-marker'&gt;+&lt;/td&gt;&lt;td style=&quot;color:black; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #a3d3ff; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;[[&lt;ins class=&quot;diffchange diffchange-inline&quot;&gt;Fichier:Fr.png|link=&lt;/ins&gt;Stripeaccounting]]&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class='diff-marker'&gt;&amp;#160;&lt;/td&gt;&lt;td style=&quot;background-color: #f9f9f9; color: #333333; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #e6e6e6; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;/td&gt;&lt;td class='diff-marker'&gt;&amp;#160;&lt;/td&gt;&lt;td style=&quot;background-color: #f9f9f9; color: #333333; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #e6e6e6; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class='diff-marker'&gt;&amp;#160;&lt;/td&gt;&lt;td style=&quot;background-color: #f9f9f9; color: #333333; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #e6e6e6; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;/td&gt;&lt;td class='diff-marker'&gt;&amp;#160;&lt;/td&gt;&lt;td style=&quot;background-color: #f9f9f9; color: #333333; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #e6e6e6; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/table&gt;</summary>
		<author><name>Goldron</name></author>	</entry>

	<entry>
		<id>https://wiki.siladel.fr/index.php?title=Stripeaccounting/en&amp;diff=546&amp;oldid=prev</id>
		<title>Goldron le 2 août 2026 à 12:22</title>
		<link rel="alternate" type="text/html" href="https://wiki.siladel.fr/index.php?title=Stripeaccounting/en&amp;diff=546&amp;oldid=prev"/>
				<updated>2026-08-02T12:22:42Z</updated>
		
		<summary type="html">&lt;p&gt;&lt;/p&gt;
&lt;table class=&quot;diff diff-contentalign-left&quot; data-mw=&quot;interface&quot;&gt;
				&lt;col class=&quot;diff-marker&quot; /&gt;
				&lt;col class=&quot;diff-content&quot; /&gt;
				&lt;col class=&quot;diff-marker&quot; /&gt;
				&lt;col class=&quot;diff-content&quot; /&gt;
				&lt;tr style=&quot;vertical-align: top;&quot; lang=&quot;fr&quot;&gt;
				&lt;td colspan=&quot;2&quot; style=&quot;background-color: white; color:black; text-align: center;&quot;&gt;← Version précédente&lt;/td&gt;
				&lt;td colspan=&quot;2&quot; style=&quot;background-color: white; color:black; text-align: center;&quot;&gt;Version du 2 août 2026 à 12:22&lt;/td&gt;
				&lt;/tr&gt;&lt;tr&gt;&lt;td colspan=&quot;2&quot; class=&quot;diff-lineno&quot; id=&quot;mw-diff-left-l1&quot; &gt;Ligne 1 :&lt;/td&gt;
&lt;td colspan=&quot;2&quot; class=&quot;diff-lineno&quot;&gt;Ligne 1 :&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class='diff-marker'&gt;&amp;#160;&lt;/td&gt;&lt;td style=&quot;background-color: #f9f9f9; color: #333333; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #e6e6e6; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;{{DISPLAYTITLE:StripeAccounting — Technical documentation}}&lt;/div&gt;&lt;/td&gt;&lt;td class='diff-marker'&gt;&amp;#160;&lt;/td&gt;&lt;td style=&quot;background-color: #f9f9f9; color: #333333; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #e6e6e6; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;{{DISPLAYTITLE:StripeAccounting — Technical documentation}}&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td colspan=&quot;2&quot;&gt;&amp;#160;&lt;/td&gt;&lt;td class='diff-marker'&gt;+&lt;/td&gt;&lt;td style=&quot;color:black; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #a3d3ff; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;&lt;ins style=&quot;font-weight: bold; text-decoration: none;&quot;&gt;&lt;/ins&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td colspan=&quot;2&quot;&gt;&amp;#160;&lt;/td&gt;&lt;td class='diff-marker'&gt;+&lt;/td&gt;&lt;td style=&quot;color:black; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #a3d3ff; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;&lt;ins style=&quot;font-weight: bold; text-decoration: none;&quot;&gt;[[Stripeaccounting]]&lt;/ins&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td colspan=&quot;2&quot;&gt;&amp;#160;&lt;/td&gt;&lt;td class='diff-marker'&gt;+&lt;/td&gt;&lt;td style=&quot;color:black; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #a3d3ff; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;&lt;ins style=&quot;font-weight: bold; text-decoration: none;&quot;&gt;&lt;/ins&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class='diff-marker'&gt;&amp;#160;&lt;/td&gt;&lt;td style=&quot;background-color: #f9f9f9; color: #333333; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #e6e6e6; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;/td&gt;&lt;td class='diff-marker'&gt;&amp;#160;&lt;/td&gt;&lt;td style=&quot;background-color: #f9f9f9; color: #333333; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #e6e6e6; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class='diff-marker'&gt;&amp;#160;&lt;/td&gt;&lt;td style=&quot;background-color: #f9f9f9; color: #333333; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #e6e6e6; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;&amp;lt;div style=&amp;quot;border:1px solid #c8ccd1; background:#f8f9fa; border-radius:4px; padding:1em 1.2em; margin:0 0 1.5em 0;&amp;quot;&amp;gt;&lt;/div&gt;&lt;/td&gt;&lt;td class='diff-marker'&gt;&amp;#160;&lt;/td&gt;&lt;td style=&quot;background-color: #f9f9f9; color: #333333; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #e6e6e6; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;&amp;lt;div style=&amp;quot;border:1px solid #c8ccd1; background:#f8f9fa; border-radius:4px; padding:1em 1.2em; margin:0 0 1.5em 0;&amp;quot;&amp;gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/table&gt;</summary>
		<author><name>Goldron</name></author>	</entry>

	<entry>
		<id>https://wiki.siladel.fr/index.php?title=Stripeaccounting/en&amp;diff=545&amp;oldid=prev</id>
		<title>Goldron : Page créée avec « {{DISPLAYTITLE:StripeAccounting — Technical documentation}}  &lt;div style=&quot;border:1px solid #c8ccd1; background:#f8f9fa; border-radius:4px; padding:1em 1.2em; margin:0 0 1... »</title>
		<link rel="alternate" type="text/html" href="https://wiki.siladel.fr/index.php?title=Stripeaccounting/en&amp;diff=545&amp;oldid=prev"/>
				<updated>2026-08-02T12:21:58Z</updated>
		
		<summary type="html">&lt;p&gt;Page créée avec « {{DISPLAYTITLE:StripeAccounting — Technical documentation}}  &amp;lt;div style=&amp;quot;border:1px solid #c8ccd1; background:#f8f9fa; border-radius:4px; padding:1em 1.2em; margin:0 0 1... »&lt;/p&gt;
&lt;p&gt;&lt;b&gt;Nouvelle page&lt;/b&gt;&lt;/p&gt;&lt;div&gt;{{DISPLAYTITLE:StripeAccounting — Technical documentation}}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;border:1px solid #c8ccd1; background:#f8f9fa; border-radius:4px; padding:1em 1.2em; margin:0 0 1.5em 0;&amp;quot;&amp;gt;&lt;br /&gt;
'''StripeAccounting''' is a Dolibarr module for '''Stripe accounting reconciliation'''.&lt;br /&gt;
&lt;br /&gt;
It reads the Stripe API (or a CSV export), identifies the movements the official Stripe module does not post — fees, refunds, disputes, payouts — and creates the missing accounting entries.&lt;br /&gt;
&lt;br /&gt;
: '''Compatibility''' — Dolibarr 17+, PHP 7.2 → 8.5&lt;br /&gt;
: '''Depends on''' — Stripe, Bank and Double-entry Accounting modules&lt;br /&gt;
: '''See also''' — [[README|Installation and user guide]]&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
__TOC__&lt;br /&gt;
&lt;br /&gt;
== 1. Design principles ==&lt;br /&gt;
&lt;br /&gt;
Four rules govern the whole module.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;width:100%;&amp;quot;&lt;br /&gt;
! style=&amp;quot;width:22%;&amp;quot; | Principle !! What it means&lt;br /&gt;
|-&lt;br /&gt;
| '''Complementary''' || The module creates no payment, no invoice, no webhook. It reads Stripe and completes the books, nothing more.&lt;br /&gt;
|-&lt;br /&gt;
| '''Idempotent''' || Each Stripe object is processed once only, no matter how many times the sync is re-run.&lt;br /&gt;
|-&lt;br /&gt;
| '''Never guess''' || If an operation cannot be matched with certainty, nothing is posted: the row goes to &amp;lt;code&amp;gt;error&amp;lt;/code&amp;gt; for manual review.&lt;br /&gt;
|-&lt;br /&gt;
| '''Configuration is blocking''' || As long as the minimum configuration is incomplete, no sync runs at all (no button, no cron, no import).&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== 2. Requirements ==&lt;br /&gt;
&lt;br /&gt;
=== Required Dolibarr modules ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Module !! What it provides&lt;br /&gt;
|-&lt;br /&gt;
| '''Stripe''' (&amp;lt;code&amp;gt;modStripe&amp;lt;/code&amp;gt;) || API key, test/live mode, Stripe financial account&lt;br /&gt;
|-&lt;br /&gt;
| '''Bank''' (&amp;lt;code&amp;gt;modBanque&amp;lt;/code&amp;gt;) || &amp;lt;code&amp;gt;llx_bank&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;llx_bank_account&amp;lt;/code&amp;gt; tables&lt;br /&gt;
|-&lt;br /&gt;
| '''Double-entry accounting''' (&amp;lt;code&amp;gt;modAccounting&amp;lt;/code&amp;gt;) || &amp;lt;code&amp;gt;llx_accounting_bookkeeping&amp;lt;/code&amp;gt; table&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== Mandatory Dolibarr setting ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;ACCOUNTING_ACCOUNT_TRANSFER_CASH&amp;lt;/code&amp;gt; must be set under '''Accounting → Setup → Miscellaneous accounts'''. Without it, the internal transfer generated by payouts cannot be transferred to accounting (a Dolibarr limitation, not a module one).&lt;br /&gt;
&lt;br /&gt;
=== Notes ===&lt;br /&gt;
&lt;br /&gt;
* The Stripe PHP SDK is not re-vendored: the module reuses the core copy (&amp;lt;code&amp;gt;includes/stripe/stripe-php/&amp;lt;/code&amp;gt;).&lt;br /&gt;
* The code deliberately avoids any PHP syntax newer than 7.2 (no &amp;lt;code&amp;gt;match&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;?-&amp;gt;&amp;lt;/code&amp;gt;, enums, typed properties, &amp;lt;code&amp;gt;str_contains&amp;lt;/code&amp;gt;…) so it runs across the whole 7.2 → 8.5 range.&lt;br /&gt;
&lt;br /&gt;
== 3. File tree ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
custom/stripeaccounting/&lt;br /&gt;
├── core/modules/modStripeAccounting.class.php   Module descriptor (permissions, menus, cron, constants)&lt;br /&gt;
├── class/&lt;br /&gt;
│   ├── StripeApi.class.php                      Stripe SDK wrapper (read-only)&lt;br /&gt;
│   ├── StripeRepository.class.php               SQL access + idempotency&lt;br /&gt;
│   ├── StripeLogger.class.php                   Logging&lt;br /&gt;
│   ├── StripeAccounting.class.php               Accounting entry generation&lt;br /&gt;
│   ├── StripePayout.class.php                   Payout reconciliation&lt;br /&gt;
│   ├── StripeSync.class.php                     Orchestrator (cron entry point)&lt;br /&gt;
│   ├── StripeCsvImporter.class.php              CSV import&lt;br /&gt;
│   └── actions_stripeaccounting.class.php       'bankcard' hook&lt;br /&gt;
├── admin/setup.php                              Configuration&lt;br /&gt;
├── import.php                                   CSV import&lt;br /&gt;
├── dashboard.php                                Dashboard&lt;br /&gt;
├── transaction_list.php                         Transaction list&lt;br /&gt;
├── cron/stripe_sync.php                         CLI entry point&lt;br /&gt;
├── lib/stripeaccounting.lib.php&lt;br /&gt;
├── sql/&lt;br /&gt;
├── langs/{fr_FR,en_US,es_ES,it_IT,de_DE}/&lt;br /&gt;
└── css/&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Dependency direction ===&lt;br /&gt;
&lt;br /&gt;
: pages and hook → &amp;lt;code&amp;gt;StripeSync&amp;lt;/code&amp;gt; → (&amp;lt;code&amp;gt;StripeApi&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;StripeRepository&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;StripeAccounting&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;StripePayout&amp;lt;/code&amp;gt;) → &amp;lt;code&amp;gt;StripeLogger&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;width:100%;&amp;quot;&lt;br /&gt;
! style=&amp;quot;width:26%;&amp;quot; | Class !! Role&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;StripeApi&amp;lt;/code&amp;gt; || Queries the Stripe SDK (balance transactions, charges, refunds, disputes, payouts) with automatic pagination. No database access, no accounting entries.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;StripeRepository&amp;lt;/code&amp;gt; || All SQL for the module's two tables. &amp;lt;code&amp;gt;upsertTransaction()&amp;lt;/code&amp;gt; is the idempotency anchor.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;StripeLogger&amp;lt;/code&amp;gt; || Writes to &amp;lt;code&amp;gt;llx_stripeaccounting_log&amp;lt;/code&amp;gt; and mirrors to &amp;lt;code&amp;gt;dol_syslog()&amp;lt;/code&amp;gt;.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;StripeAccounting&amp;lt;/code&amp;gt; || Builds accounting entries (refund, dispute, reverse-charge VAT) or bank movements (fees). Exposes &amp;lt;code&amp;gt;checkConfig()&amp;lt;/code&amp;gt;.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;StripePayout&amp;lt;/code&amp;gt; || Creates the internal bank transfer between the Stripe account and the real bank account.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;StripeSync&amp;lt;/code&amp;gt; || Orchestrator: one method per flow, plus &amp;lt;code&amp;gt;reconcileStripeAccount()&amp;lt;/code&amp;gt; which chains them all.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;StripeCsvImporter&amp;lt;/code&amp;gt; || Feeds the same pipeline from a Stripe CSV export.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;ActionsStripeAccounting&amp;lt;/code&amp;gt; || &amp;lt;code&amp;gt;bankcard&amp;lt;/code&amp;gt; hook: dashboard link on the Stripe account record.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== 4. Synchronisation and idempotency ==&lt;br /&gt;
&lt;br /&gt;
Every flow (&amp;lt;code&amp;gt;syncFees&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;syncRefunds&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;syncDisputes&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;syncPayouts&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;syncTransactions&amp;lt;/code&amp;gt;) always follows the same steps:&lt;br /&gt;
&lt;br /&gt;
# '''Check the configuration''' — if incomplete: &amp;lt;code&amp;gt;ERROR&amp;lt;/code&amp;gt; log, immediate stop, no API call at all.&lt;br /&gt;
# '''List Stripe objects''' over a rolling window of '''35 days'''.&lt;br /&gt;
# '''Filter''' — an object is skipped only if it already exists ''and'' its status is &amp;lt;code&amp;gt;reconciled&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;ignored&amp;lt;/code&amp;gt;. A row in &amp;lt;code&amp;gt;pending&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;error&amp;lt;/code&amp;gt; is '''retried''' on this run.&lt;br /&gt;
# '''Record''' it as &amp;lt;code&amp;gt;pending&amp;lt;/code&amp;gt;, then generate the accounting entry or bank movement.&lt;br /&gt;
# '''Conclude''' — success: status &amp;lt;code&amp;gt;reconciled&amp;lt;/code&amp;gt;. Failure: status &amp;lt;code&amp;gt;error&amp;lt;/code&amp;gt; with the message, and '''the batch carries on''' with the next object.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;border-left:4px solid #36c; background:#eaf3ff; padding:0.6em 1em; margin:1em 0;&amp;quot;&amp;gt;&lt;br /&gt;
'''Automatic retry''' — a row in &amp;lt;code&amp;gt;error&amp;lt;/code&amp;gt; becomes eligible again on the next run. Once the cause is fixed (a fiscal year created late, for instance), no manual action is needed.&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Why duplicates are impossible ===&lt;br /&gt;
&lt;br /&gt;
* '''Unique database key''' on &amp;lt;code&amp;gt;(stripe_object_id, entity)&amp;lt;/code&amp;gt; — protects even against two concurrent cron executions.&lt;br /&gt;
* '''Explicit check before processing''' — avoids even attempting to post an entry for an already-finalised object.&lt;br /&gt;
&lt;br /&gt;
=== Special cases: &amp;lt;code&amp;gt;adjustment&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;payment_failure_refund&amp;lt;/code&amp;gt; ===&lt;br /&gt;
&lt;br /&gt;
These are balance corrections Stripe makes on its own (reversal after a dispute is resolved, automatic refund of a payment that ultimately failed). The module '''records''' them — otherwise they would stay invisible — but '''never''' posts them automatically: the sign of the amount and the underlying cause vary too much. They are created directly in &amp;lt;code&amp;gt;error&amp;lt;/code&amp;gt;, with an explicit message, to be handled manually.&lt;br /&gt;
&lt;br /&gt;
== 5. Accounting entries ==&lt;br /&gt;
&lt;br /&gt;
All lines of a single Stripe event share the same &amp;lt;code&amp;gt;doc_type='stripeaccounting'&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;fk_doc&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;doc_ref&amp;lt;/code&amp;gt; triplet: Dolibarr automatically groups them under one piece number, so the module needs no numbering scheme of its own.&lt;br /&gt;
&lt;br /&gt;
=== Overview ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;width:100%;&amp;quot;&lt;br /&gt;
! Event !! What the module does !! Resulting entry&lt;br /&gt;
|-&lt;br /&gt;
| Stripe fee ''(VAT mode &amp;lt;code&amp;gt;none&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;autoliquidation&amp;lt;/code&amp;gt;)'' || &amp;lt;code&amp;gt;PaymentVarious&amp;lt;/code&amp;gt; bank movement || 627xxx / 517xxx entry generated later by Dolibarr&lt;br /&gt;
|-&lt;br /&gt;
| Stripe fee ''(&amp;lt;code&amp;gt;autoliquidation&amp;lt;/code&amp;gt; mode)'' || Plus: VAT pair posted immediately || Debit deductible VAT, credit reverse-charge VAT ''(net impact nil)''&lt;br /&gt;
|-&lt;br /&gt;
| Stripe fee ''(&amp;lt;code&amp;gt;french&amp;lt;/code&amp;gt; mode)'' || Full direct entry || Debit fee account excl. VAT + deductible VAT, credit Stripe account incl. VAT&lt;br /&gt;
|-&lt;br /&gt;
| Subscription / service fees ''(Radar, Billing, Connect…)'' || &amp;lt;code&amp;gt;PaymentVarious&amp;lt;/code&amp;gt; bank movement || Entry generated later by Dolibarr, on the Stripe supplier account&lt;br /&gt;
|-&lt;br /&gt;
| Payout || Internal bank transfer between the two accounts || Entry generated later by Dolibarr&lt;br /&gt;
|-&lt;br /&gt;
| Refund ''(invoice found)'' || Direct entry || Debit customer account 411xxx, credit Stripe account 517xxx&lt;br /&gt;
|-&lt;br /&gt;
| Refund ''(invoice not found)'' || Nothing || Status &amp;lt;code&amp;gt;error&amp;lt;/code&amp;gt;, handle manually&lt;br /&gt;
|-&lt;br /&gt;
| Dispute / chargeback || Direct entry || Debit dispute loss 658xxx, credit Stripe account 517xxx ''(+ fee account if fees apply)''&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== Why fees and payouts produce no direct entry ===&lt;br /&gt;
&lt;br /&gt;
The historical problem: when a Stripe-paid invoice is transferred to accounting through Dolibarr's standard workflow, the payment is posted at its '''gross''' amount — fees are ignored at that stage.&lt;br /&gt;
&lt;br /&gt;
If the module posted its own entry ''and'' its own bank line on top of that, this line would stay invisible to &amp;lt;code&amp;gt;bankjournal.php&amp;lt;/code&amp;gt; — which only recognises lines attached to &amp;lt;code&amp;gt;doc_type='bank'&amp;lt;/code&amp;gt;. An administrator transferring the bank account to accounting &amp;quot;normally&amp;quot; would then create a '''second''' entry for the very same movement.&lt;br /&gt;
&lt;br /&gt;
'''The chosen solution:''' for fees, subscription fees and payouts, the module now creates only the '''real bank movement''', using Dolibarr's native mechanisms. The native ''&amp;quot;Transfer to accounting&amp;quot;'' button then produces the entry, exactly as for any other bank movement. In those three cases, the module no longer touches &amp;lt;code&amp;gt;llx_accounting_bookkeeping&amp;lt;/code&amp;gt; at all.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;width:100%;&amp;quot;&lt;br /&gt;
! style=&amp;quot;width:22%;&amp;quot; | Case !! Native mechanism used&lt;br /&gt;
|-&lt;br /&gt;
| Fees and subscription fees || &amp;lt;code&amp;gt;PaymentVarious&amp;lt;/code&amp;gt; (bank movement with an explicit counterpart account, no third party or invoice), carrying its own &amp;lt;code&amp;gt;accountancy_code&amp;lt;/code&amp;gt;, which &amp;lt;code&amp;gt;bankjournal.php&amp;lt;/code&amp;gt; reads directly.&lt;br /&gt;
|-&lt;br /&gt;
| Payouts || Dolibarr internal transfer: &amp;lt;code&amp;gt;Account::addline()&amp;lt;/code&amp;gt; on both sides, plus &amp;lt;code&amp;gt;add_url_line(…, 'banktransfert')&amp;lt;/code&amp;gt;. The counterpart is posted to &amp;lt;code&amp;gt;ACCOUNTING_ACCOUNT_TRANSFER_CASH&amp;lt;/code&amp;gt;.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
In both cases the status becomes &amp;lt;code&amp;gt;reconciled&amp;lt;/code&amp;gt; — the module's job is done — but '''&amp;lt;code&amp;gt;piece_num&amp;lt;/code&amp;gt; stays empty''': the entry does not exist yet, only &amp;lt;code&amp;gt;fk_bank&amp;lt;/code&amp;gt; is set. After the manual transfer, it can be found with &amp;lt;code&amp;gt;doc_type='bank'&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;fk_doc&amp;lt;/code&amp;gt; equal to the row's &amp;lt;code&amp;gt;fk_bank&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;border-left:4px solid #fc3; background:#fef6e7; padding:0.6em 1em; margin:1em 0;&amp;quot;&amp;gt;&lt;br /&gt;
'''Worth knowing''' — the payout entry only balances to zero on the transfer account once '''both''' bank accounts (Stripe ''and'' the real bank) have been transferred to accounting, not just one of them.&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;border-left:4px solid #d33; background:#fee7e6; padding:0.6em 1em; margin:1em 0;&amp;quot;&amp;gt;&lt;br /&gt;
'''⚠ &amp;lt;code&amp;gt;STRIPE_AUTO_RECORD_PAYOUT&amp;lt;/code&amp;gt; must stay disabled'''&lt;br /&gt;
&lt;br /&gt;
Once this module handles payouts, untick &amp;quot;Enable automatic recording of Stripe payments&amp;quot; in &amp;lt;code&amp;gt;stripe/admin/stripe.php&amp;lt;/code&amp;gt; — despite its wording, that checkbox only concerns payouts.&lt;br /&gt;
&lt;br /&gt;
Otherwise the official module's webhook and this module would each create their own bank movement for the same payout, unaware of each other.&lt;br /&gt;
&lt;br /&gt;
The core webhook keeps '''no''' trace of the payout id it has processed: should Stripe deliver the event twice (delivery is guaranteed &amp;quot;at least once&amp;quot;), nothing stops it from duplicating on its own either. This module has real idempotency and automatically catches up any missed payout within 35 days.&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== The &amp;quot;Stripe transfers&amp;quot; page is hidden ===&lt;br /&gt;
&lt;br /&gt;
On activation, &amp;lt;code&amp;gt;modStripeAccounting::init()&amp;lt;/code&amp;gt; disables the &amp;lt;code&amp;gt;stripe/payout.php&amp;lt;/code&amp;gt; menu entry: that page queries the API live without recording anything, and having it alongside this module would only create confusion about which one is authoritative for payouts. &amp;lt;code&amp;gt;remove()&amp;lt;/code&amp;gt; restores the original condition. '''This is the only core table the module modifies''' — deliberate, and documented here for that reason.&lt;br /&gt;
&lt;br /&gt;
=== Per-transaction fees or subscription fees? ===&lt;br /&gt;
&lt;br /&gt;
Stripe bills two distinct things under the generic word &amp;quot;fees&amp;quot;:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;width:100%;&amp;quot;&lt;br /&gt;
! !! Per-transaction fees !! Subscription / service fees&lt;br /&gt;
|-&lt;br /&gt;
! Example&lt;br /&gt;
| Commission on each payment || Radar, Billing, Connect&lt;br /&gt;
|-&lt;br /&gt;
! Where to find it&lt;br /&gt;
| The &amp;lt;code&amp;gt;.fee&amp;lt;/code&amp;gt; field of a &amp;lt;code&amp;gt;charge&amp;lt;/code&amp;gt; balance transaction || A dedicated balance transaction, type &amp;lt;code&amp;gt;stripe_fee&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;application_fee&amp;lt;/code&amp;gt; ''(its &amp;lt;code&amp;gt;.fee&amp;lt;/code&amp;gt; is always 0, the amount sits in &amp;lt;code&amp;gt;.amount&amp;lt;/code&amp;gt;)''&lt;br /&gt;
|-&lt;br /&gt;
! Account used&lt;br /&gt;
| The configured fee account (627xxx) || The supplier accounting code of the configured Stripe third party&lt;br /&gt;
|-&lt;br /&gt;
! Type in database&lt;br /&gt;
| &amp;lt;code&amp;gt;fee&amp;lt;/code&amp;gt; || &amp;lt;code&amp;gt;subscription_fee&amp;lt;/code&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== Limitation of the &amp;lt;code&amp;gt;french&amp;lt;/code&amp;gt; VAT mode ===&lt;br /&gt;
&lt;br /&gt;
This mode has to split the gross amount into net + VAT, something &amp;lt;code&amp;gt;PaymentVarious&amp;lt;/code&amp;gt; cannot express. It therefore keeps the previous behaviour: a full direct entry, with no bank movement created. No duplicate risk (precisely because no bank line is produced), but the &amp;lt;code&amp;gt;llx_bank&amp;lt;/code&amp;gt; balance of the Stripe account will never reflect the fees while this mode is active.&lt;br /&gt;
&lt;br /&gt;
== 6. CSV import ==&lt;br /&gt;
&lt;br /&gt;
The import covers the case of a Stripe account '''different''' from the one configured in the official module — another PrestaShop shop with its own merchant account, for example. The API has no access to it: its contents are exported to CSV from the Stripe dashboard, then imported.&lt;br /&gt;
&lt;br /&gt;
=== Starting assumption ===&lt;br /&gt;
&lt;br /&gt;
The invoices and payments of that shop are '''already''' posted in Dolibarr through some other process, and the collection (debit 517, credit 411) is already booked. Only the '''fees''', '''refunds''' and '''payouts''' are missing — which is exactly what the import handles.&lt;br /&gt;
&lt;br /&gt;
=== Interface: a single field ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;import.php&amp;lt;/code&amp;gt; now exposes only one upload field, '''General history''', wired to &amp;lt;code&amp;gt;importGeneralHistory()&amp;lt;/code&amp;gt;. That file alone covers every movement type.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;importPayments()&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;importPayouts()&amp;lt;/code&amp;gt; are still implemented and functional, but no longer reachable from the interface; they are documented below for reference.&lt;br /&gt;
&lt;br /&gt;
Like the &amp;quot;Sync now&amp;quot; button, the import is blocked — warning banner, disabled button, server-side guard — while the configuration is incomplete.&lt;br /&gt;
&lt;br /&gt;
=== The three methods ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;width:100%;&amp;quot;&lt;br /&gt;
! style=&amp;quot;width:24%;&amp;quot; | Method !! Stripe export read !! Processing&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;importGeneralHistory()&amp;lt;/code&amp;gt;&amp;lt;br /&amp;gt;''(the only one exposed)'' || Balance history || Dispatch on the &amp;lt;code&amp;gt;Type&amp;lt;/code&amp;gt; column, see table below&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;importPayments()&amp;lt;/code&amp;gt; || Payments || Fee → fee movement. Amount refunded → &amp;lt;code&amp;gt;refund&amp;lt;/code&amp;gt; row, always in &amp;lt;code&amp;gt;error&amp;lt;/code&amp;gt;.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;importPayouts()&amp;lt;/code&amp;gt; || Payouts || Exactly the same processing as a payout coming from the API&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
General history dispatch, by &amp;lt;code&amp;gt;Type&amp;lt;/code&amp;gt; column:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;width:100%;&amp;quot;&lt;br /&gt;
! style=&amp;quot;width:30%;&amp;quot; | Stripe type !! Processing&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;payout&amp;lt;/code&amp;gt; || Internal bank transfer, as via the API&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;charge&amp;lt;/code&amp;gt; / &amp;lt;code&amp;gt;payment&amp;lt;/code&amp;gt; with a fee || Fee movement&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;stripe_fee&amp;lt;/code&amp;gt; / &amp;lt;code&amp;gt;application_fee&amp;lt;/code&amp;gt; || Subscription fee, on the Stripe supplier account&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;refund&amp;lt;/code&amp;gt; || Recorded, '''always''' in &amp;lt;code&amp;gt;error&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;payout_minimum_balance_hold&amp;lt;/code&amp;gt; / &amp;lt;code&amp;gt;…_release&amp;lt;/code&amp;gt; || '''Ignored''' — Stripe rolling reserve, no real cash movement, nothing to post&lt;br /&gt;
|-&lt;br /&gt;
| any other type || Recorded as &amp;lt;code&amp;gt;type='other'&amp;lt;/code&amp;gt;, status &amp;lt;code&amp;gt;error&amp;lt;/code&amp;gt;, raw Stripe type kept in the description. No guessed entry.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Columns are matched '''by name''': their order does not matter, and extra unused columns are ignored.&lt;br /&gt;
&lt;br /&gt;
=== Import idempotency ===&lt;br /&gt;
&lt;br /&gt;
The unique key protects against any double import: re-importing the same CSV, or one overlapping a previous import, creates no duplicate.&lt;br /&gt;
&lt;br /&gt;
An important detail: in the general history, &amp;lt;code&amp;gt;payout&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;charge&amp;lt;/code&amp;gt;/&amp;lt;code&amp;gt;payment&amp;lt;/code&amp;gt; rows are indexed by their '''&amp;lt;code&amp;gt;Source&amp;lt;/code&amp;gt;''' column (&amp;lt;code&amp;gt;po_…&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;ch_…&amp;lt;/code&amp;gt;), not by the row's own &amp;lt;code&amp;gt;id&amp;lt;/code&amp;gt; (&amp;lt;code&amp;gt;txn_…&amp;lt;/code&amp;gt;) — the very same key the dedicated imports use. Importing the general history '''on top of''' the specific exports therefore never posts the same entry twice, in any order.&lt;br /&gt;
&lt;br /&gt;
Since Stripe identifiers are unique platform-wide, mixing rows from the API account and from several CSV-imported accounts in the same table carries no collision risk.&lt;br /&gt;
&lt;br /&gt;
=== Practical details ===&lt;br /&gt;
&lt;br /&gt;
* '''Source label''' — a free-text field entered at import time (e.g. &amp;quot;DRM shop&amp;quot;) is stored on every created row and serves as a column and filter in the list, to tell several shops apart side by side.&lt;br /&gt;
* '''Amount format''' — Stripe CSVs use the French decimal comma, converted through &amp;lt;code&amp;gt;price2num()&amp;lt;/code&amp;gt;. Dates marked &amp;lt;code&amp;gt;(UTC)&amp;lt;/code&amp;gt; are interpreted as such.&lt;br /&gt;
&lt;br /&gt;
== 7. Configuration ==&lt;br /&gt;
&lt;br /&gt;
The API key and test/live mode are '''not''' re-entered here: they are read, read-only, from the official Stripe module.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;width:100%;&amp;quot;&lt;br /&gt;
! style=&amp;quot;width:26%;&amp;quot; | Field !! Constant !! Purpose&lt;br /&gt;
|-&lt;br /&gt;
| Stripe financial account || &amp;lt;code&amp;gt;STRIPE_BANK_ACCOUNT_FOR_PAYMENTS&amp;lt;/code&amp;gt; ''(official module constant)'' || Bank account selector. No local copy, hence no risk of divergence.&lt;br /&gt;
|-&lt;br /&gt;
| Main bank account || &amp;lt;code&amp;gt;STRIPE_BANK_ACCOUNT_FOR_BANKTRANSFERS&amp;lt;/code&amp;gt; ''(official module constant)'' || Same selector. See the box below.&lt;br /&gt;
|-&lt;br /&gt;
| Fee account || &amp;lt;code&amp;gt;STRIPEACCOUNTING_COMPTE_FRAIS&amp;lt;/code&amp;gt; || Account number, e.g. 627xxx&lt;br /&gt;
|-&lt;br /&gt;
| Dispute account || &amp;lt;code&amp;gt;STRIPEACCOUNTING_COMPTE_LITIGES&amp;lt;/code&amp;gt; || Account number, e.g. 658xxx. '''Required''' — used as soon as a dispute occurs, whatever the VAT mode.&lt;br /&gt;
|-&lt;br /&gt;
| Stripe supplier third party || &amp;lt;code&amp;gt;STRIPEACCOUNTING_FOURNISSEUR_STRIPE_ID&amp;lt;/code&amp;gt; || A third party flagged as supplier; its supplier accounting code is what gets used. Optional, unless subscription fees exist.&lt;br /&gt;
|-&lt;br /&gt;
| VAT mode || &amp;lt;code&amp;gt;STRIPEACCOUNTING_VAT_MODE&amp;lt;/code&amp;gt; || &amp;lt;code&amp;gt;none&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;french&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;autoliquidation&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| Deductible VAT account (intra-EU) || &amp;lt;code&amp;gt;STRIPEACCOUNTING_COMPTE_TVA_DEDUCTIBLE&amp;lt;/code&amp;gt; || Required when the VAT mode is not &amp;lt;code&amp;gt;none&amp;lt;/code&amp;gt;. Stripe always invoices from a non-French EU entity, so the deductible VAT is intra-EU in every case.&lt;br /&gt;
|-&lt;br /&gt;
| VAT due account (intra-EU) || &amp;lt;code&amp;gt;STRIPEACCOUNTING_COMPTE_TVA_AUTOLIQUIDATION&amp;lt;/code&amp;gt; || Required in &amp;lt;code&amp;gt;autoliquidation&amp;lt;/code&amp;gt; mode only — never used in &amp;lt;code&amp;gt;french&amp;lt;/code&amp;gt; mode.&lt;br /&gt;
|-&lt;br /&gt;
| VAT rate || &amp;lt;code&amp;gt;STRIPEACCOUNTING_VAT_RATE&amp;lt;/code&amp;gt; || Percentage, 20 by default&lt;br /&gt;
|-&lt;br /&gt;
| Sync frequency || &amp;lt;code&amp;gt;STRIPEACCOUNTING_SYNC_UNITFREQUENCY&amp;lt;/code&amp;gt; || Applied to the scheduled job (in seconds)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;border-left:4px solid #36c; background:#eaf3ff; padding:0.6em 1em; margin:1em 0;&amp;quot;&amp;gt;&lt;br /&gt;
'''Why redefine the main bank account here?'''&lt;br /&gt;
&lt;br /&gt;
The official Stripe module's setup page only renders that field when &amp;lt;code&amp;gt;STRIPE_AUTO_RECORD_PAYOUT&amp;lt;/code&amp;gt; is enabled — and its own save handler resets the value to 0 on every save while the field is not rendered. Yet that setting must stay disabled once this module handles payouts. The field is therefore re-exposed here, writing the core constant directly, so the value stays configurable and stable.&lt;br /&gt;
&lt;br /&gt;
Both account selectors only write their constant when a real account was submitted: saving the page for some unrelated field never overwrites a correct value.&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Configuration check ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;StripeAccounting::checkConfig()&amp;lt;/code&amp;gt; returns the list of missing constants. It is called both by the setup page (information banner) and by every sync entry point (the real guard that blocks all processing).&lt;br /&gt;
&lt;br /&gt;
=== Pitfall with accounting account selectors ===&lt;br /&gt;
&lt;br /&gt;
The four fields using &amp;lt;code&amp;gt;FormAccounting::select_account()&amp;lt;/code&amp;gt; (fees, disputes, both VAT accounts) have a &amp;quot;nothing selected&amp;quot; option whose value is '''&amp;lt;code&amp;gt;-1&amp;lt;/code&amp;gt;''' — a non-empty string, therefore truthy in PHP. A field left blank and saved literally stores &amp;lt;code&amp;gt;-1&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;StripeAccounting::getAccountConst()&amp;lt;/code&amp;gt; normalises that value to an empty string on read, everywhere these constants are read. No entry can therefore land on the non-existent account &amp;lt;code&amp;gt;-1&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== 8. User interface ==&lt;br /&gt;
&lt;br /&gt;
=== Dashboard ===&lt;br /&gt;
&lt;br /&gt;
Stripe balance (live API call), number of unreconciled transactions, total fees for the month, pending payouts, and a '''Sync now''' button — disabled, with a banner listing the missing constants, while the configuration is incomplete.&lt;br /&gt;
&lt;br /&gt;
=== Transaction list ===&lt;br /&gt;
&lt;br /&gt;
A paginated, sortable, filterable list built with the same mechanism as native Dolibarr lists: the icon at the top right of the table lets each user pick their columns, and the preference is stored per user.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;width:100%;&amp;quot;&lt;br /&gt;
! style=&amp;quot;width:20%;&amp;quot; | Column !! Content&lt;br /&gt;
|-&lt;br /&gt;
| Amount, Fee, Total || The fee popup shows the VAT on the fee whenever a reverse-charge pair or French VAT was posted&lt;br /&gt;
|-&lt;br /&gt;
| Type, Description || Popup with the Stripe ID, the event or source ID, and the full untruncated description&lt;br /&gt;
|-&lt;br /&gt;
| Third party || The customer resolved through the invoice, or through the source charge for its related fee, or the configured Stripe supplier for a subscription fee&lt;br /&gt;
|-&lt;br /&gt;
| Entry || A &amp;quot;Piece no. X&amp;quot; link to the accounting entry, and/or a link to the related bank movement — see the caveat below&lt;br /&gt;
|-&lt;br /&gt;
| Source, Status || The &amp;lt;code&amp;gt;error&amp;lt;/code&amp;gt; badge opens a popup with the full error message: no need to query the database to understand a failure&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;border-left:4px solid #fc3; background:#fef6e7; padding:0.6em 1em; margin:1em 0;&amp;quot;&amp;gt;&lt;br /&gt;
'''Reading the &amp;quot;Entry&amp;quot; column''' — on a &amp;lt;code&amp;gt;fee&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;subscription_fee&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;payout&amp;lt;/code&amp;gt; row, the link points to the '''bank movement''', not to an accounting piece. The module's &amp;lt;code&amp;gt;piece_num&amp;lt;/code&amp;gt; is never the 627/517 entry for the fee: it is only the reverse-charge VAT pair. The fee entry itself is generated later, by Dolibarr's native button, and is never reported back onto this row.&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The page is '''purely informational''': no manual posting is offered there. The module posts automatically, and the actual transfer to accounting for fees and payouts happens through the native &amp;quot;Transfer to accounting&amp;quot; button.&lt;br /&gt;
&lt;br /&gt;
=== &amp;lt;code&amp;gt;charge&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;fee&amp;lt;/code&amp;gt; rows ===&lt;br /&gt;
&lt;br /&gt;
Each payment produces '''two rows''' in the database:&lt;br /&gt;
&lt;br /&gt;
* a &amp;lt;code&amp;gt;charge&amp;lt;/code&amp;gt; row, always in status &amp;lt;code&amp;gt;ignored&amp;lt;/code&amp;gt;, fee 0, never processed — present solely for idempotency;&lt;br /&gt;
* a &amp;lt;code&amp;gt;fee&amp;lt;/code&amp;gt; row for the same payment, linked to the first one.&lt;br /&gt;
&lt;br /&gt;
Both appear separately in the list, but the &amp;lt;code&amp;gt;fee&amp;lt;/code&amp;gt; row also carries the link to the payment and the invoice, resolved through a join.&lt;br /&gt;
&lt;br /&gt;
=== Test mode banner ===&lt;br /&gt;
&lt;br /&gt;
While live mode is off, the dashboard, the setup page and the transaction list all display the same warning as the official Stripe module. It is impossible to forget you are working in test mode.&lt;br /&gt;
&lt;br /&gt;
=== Menus ===&lt;br /&gt;
&lt;br /&gt;
Everything sits under '''Banks and cash → Stripe account''' (the official module's menu), flat, as three entries at the same level:&lt;br /&gt;
&lt;br /&gt;
# Stripe dashboard&lt;br /&gt;
# Stripe transactions&lt;br /&gt;
# CSV import ''(administrators only)''&lt;br /&gt;
&lt;br /&gt;
A dashboard link is also added automatically on the bank account record configured as the Stripe account.&lt;br /&gt;
&lt;br /&gt;
== 9. Scheduled job ==&lt;br /&gt;
&lt;br /&gt;
There are two ways to run the sync, both idempotent and safe to re-run. In both cases the code executed is strictly the same: no behavioural difference between the two triggers.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;border-left:4px solid #d33; background:#fee7e6; padding:0.6em 1em; margin:1em 0;&amp;quot;&amp;gt;&lt;br /&gt;
'''⚠ Neither of them starts on its own.'''&lt;br /&gt;
&lt;br /&gt;
Activating the module creates the &amp;lt;code&amp;gt;StripeAccountingSync&amp;lt;/code&amp;gt; job, but '''disabled by default'''. And even once enabled, the Dolibarr scheduler is triggered by nothing until a system crontab calls it. Without both steps, the sync only happens when &amp;quot;Sync now&amp;quot; is clicked manually.&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Step 1 — enable the job ===&lt;br /&gt;
&lt;br /&gt;
Under '''Home → Setup → Modules → Scheduled jobs'''. Since &amp;lt;code&amp;gt;llx_cronjob&amp;lt;/code&amp;gt; is a core table, this setting is made through the interface only, never by direct SQL from the module.&lt;br /&gt;
&lt;br /&gt;
=== Step 2 — a system trigger ===&lt;br /&gt;
&lt;br /&gt;
Pick either option.&lt;br /&gt;
&lt;br /&gt;
'''Option A — Dolibarr scheduler''' ''(recommended when several Dolibarr scheduled jobs coexist)''. Runs every due job, not just this one. Requires step 1.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
*/15 * * * * curl &amp;quot;https://your-dolibarr/public/cron/cron_run_jobs_by_url.php?securitykey=YOUR_KEY&amp;amp;userlogin=YOUR_LOGIN&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The security key is generated on the scheduled jobs page, &amp;quot;Security of job execution by URL&amp;quot; button.&lt;br /&gt;
&lt;br /&gt;
'''Option B — the module's CLI script.''' Standalone, refuses to run from a browser, and independent of whether the Dolibarr job is enabled.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
*/15 * * * * php /path/to/htdocs/custom/stripeaccounting/cron/stripe_sync.php &amp;gt;&amp;gt; /var/log/stripeaccounting.log 2&amp;gt;&amp;amp;1&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 10. Logging and permissions ==&lt;br /&gt;
&lt;br /&gt;
=== Logging ===&lt;br /&gt;
&lt;br /&gt;
Every API call, generated entry, warning or error is traced in &amp;lt;code&amp;gt;llx_stripeaccounting_log&amp;lt;/code&amp;gt;, readable in the database or through the standard Dolibarr log file. Levels: &amp;lt;code&amp;gt;DEBUG&amp;lt;/code&amp;gt; &amp;lt; &amp;lt;code&amp;gt;INFO&amp;lt;/code&amp;gt; &amp;lt; &amp;lt;code&amp;gt;WARNING&amp;lt;/code&amp;gt; &amp;lt; &amp;lt;code&amp;gt;ERROR&amp;lt;/code&amp;gt;. The &amp;lt;code&amp;gt;context&amp;lt;/code&amp;gt; column holds the structured detail as JSON (API payload, computed amounts…).&lt;br /&gt;
&lt;br /&gt;
=== Permissions ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Right !! Controls&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;read&amp;lt;/code&amp;gt; || Access to the dashboard and the transaction list&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;write&amp;lt;/code&amp;gt; || Declared for configuration, but not checked in the code today&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;reconcile&amp;lt;/code&amp;gt; || Manual sync triggering&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
In practice, &amp;lt;code&amp;gt;admin/setup.php&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;import.php&amp;lt;/code&amp;gt; only check '''Dolibarr administrator''' status, not the &amp;lt;code&amp;gt;write&amp;lt;/code&amp;gt; right: only administrators can configure the module or import a CSV, whatever is ticked in the module permissions.&lt;br /&gt;
&lt;br /&gt;
== 11. Database schema ==&lt;br /&gt;
&lt;br /&gt;
=== &amp;lt;code&amp;gt;llx_stripeaccounting_transaction&amp;lt;/code&amp;gt; ===&lt;br /&gt;
&lt;br /&gt;
One row per processed Stripe object — charge, fee, refund, dispute or payout — all in the same table, distinguished by the &amp;lt;code&amp;gt;type&amp;lt;/code&amp;gt; column.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;width:100%;&amp;quot;&lt;br /&gt;
! style=&amp;quot;width:24%;&amp;quot; | Column !! style=&amp;quot;width:16%;&amp;quot; | Type !! Role&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;rowid&amp;lt;/code&amp;gt; || int AI || Primary key&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;entity&amp;lt;/code&amp;gt; || int || Dolibarr multi-company&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;stripe_object_id&amp;lt;/code&amp;gt; || varchar(80) || Stripe identifier — '''idempotency anchor'''&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;stripe_event_id&amp;lt;/code&amp;gt; || varchar(80) || Event identifier, informational only&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;type&amp;lt;/code&amp;gt; || varchar(20) || &amp;lt;code&amp;gt;charge&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;fee&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;subscription_fee&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;refund&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;dispute&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;payout&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;other&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;stripe_source_id&amp;lt;/code&amp;gt; || varchar(80) || Source Stripe object (e.g. the charge behind a refund)&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;source_label&amp;lt;/code&amp;gt; || varchar(80) || Free-text label entered at CSV import; empty for API rows&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;description&amp;lt;/code&amp;gt; || varchar(255) || Stripe label&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;amount&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;fee&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;net&amp;lt;/code&amp;gt; || decimal(24,8) || Amounts already converted from Stripe cents&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;currency&amp;lt;/code&amp;gt; || varchar(3) || ISO currency code&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;date_created&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;date_available_on&amp;lt;/code&amp;gt; || datetime || Stripe-side dates&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;fk_facture&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;fk_paiement&amp;lt;/code&amp;gt; || int || Resolved Dolibarr invoice and payment, if found&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;fk_bank&amp;lt;/code&amp;gt; || int || Generated &amp;lt;code&amp;gt;llx_bank&amp;lt;/code&amp;gt; line ''(empty for a fee in &amp;lt;code&amp;gt;french&amp;lt;/code&amp;gt; VAT mode)''&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;piece_num&amp;lt;/code&amp;gt; || varchar(20) || Generated accounting piece number, where there is one&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;status&amp;lt;/code&amp;gt; || varchar(16) || &amp;lt;code&amp;gt;pending&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;reconciled&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;error&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;ignored&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;error_message&amp;lt;/code&amp;gt; || text || Detail on failure&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;date_creation&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;date_processed&amp;lt;/code&amp;gt; || datetime || Technical timestamps&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;fk_user_creat&amp;lt;/code&amp;gt; || int || User who triggered the processing&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
'''Unique key''' on &amp;lt;code&amp;gt;(stripe_object_id, entity)&amp;lt;/code&amp;gt; — this is what guarantees the same Stripe object can never be inserted twice, even with concurrent executions.&lt;br /&gt;
&lt;br /&gt;
=== &amp;lt;code&amp;gt;llx_stripeaccounting_log&amp;lt;/code&amp;gt; ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Column !! Role&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;level&amp;lt;/code&amp;gt; || &amp;lt;code&amp;gt;DEBUG&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;INFO&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;WARNING&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;ERROR&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;message&amp;lt;/code&amp;gt; || Short message&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;context&amp;lt;/code&amp;gt; || Structured context as JSON&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;fk_stripeaccounting_transaction&amp;lt;/code&amp;gt; || Related transaction, where applicable&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;datec&amp;lt;/code&amp;gt; || Timestamp&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== 12. Troubleshooting ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;width:100%;&amp;quot;&lt;br /&gt;
! style=&amp;quot;width:26%;&amp;quot; | Symptom !! Likely cause !! Action&lt;br /&gt;
|-&lt;br /&gt;
| The &amp;quot;Sync now&amp;quot; button is disabled || Minimum configuration incomplete || Fill in the fields listed in the setup page banner&lt;br /&gt;
|-&lt;br /&gt;
| A transaction stays in &amp;lt;code&amp;gt;error&amp;lt;/code&amp;gt; || Entry rejected by Dolibarr (non-existent account, closed period), or refund not matched || Read &amp;lt;code&amp;gt;error_message&amp;lt;/code&amp;gt; on the row, then the detail in the log table&lt;br /&gt;
|-&lt;br /&gt;
| The Stripe balance is not displayed || API key missing or invalid in the official Stripe module || Check &amp;lt;code&amp;gt;stripe/admin/stripe.php&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| A refund is never reconciled || The original payment was not found through &amp;lt;code&amp;gt;ext_payment_id&amp;lt;/code&amp;gt; || Check that the Stripe payment was recorded by the official module with that identifier&lt;br /&gt;
|-&lt;br /&gt;
| A &amp;lt;code&amp;gt;subscription_fee&amp;lt;/code&amp;gt; stays in &amp;lt;code&amp;gt;error&amp;lt;/code&amp;gt; || Stripe supplier third party not set, or has no supplier accounting code || Fill the field in setup, and complete the third party record&lt;br /&gt;
|-&lt;br /&gt;
| A CSV-imported refund stays in &amp;lt;code&amp;gt;error&amp;lt;/code&amp;gt; || '''Normal and expected''' — that account is not linked to &amp;lt;code&amp;gt;ext_payment_id&amp;lt;/code&amp;gt; || Handle the invoice and refund manually in accounting&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== 13. Pitfalls encountered ==&lt;br /&gt;
&lt;br /&gt;
Development notes, kept so nobody falls into them again.&lt;br /&gt;
&lt;br /&gt;
* '''&amp;lt;code&amp;gt;PaymentVarious::create()&amp;lt;/code&amp;gt;''' only updates &amp;lt;code&amp;gt;fk_bank&amp;lt;/code&amp;gt; in the database, never on the PHP object. A &amp;lt;code&amp;gt;fetch()&amp;lt;/code&amp;gt; after &amp;lt;code&amp;gt;create()&amp;lt;/code&amp;gt; is required to get the real value.&lt;br /&gt;
* '''&amp;lt;code&amp;gt;select_comptes()&amp;lt;/code&amp;gt;''' prints its own HTML ''and'' returns the number of accounts. Call it as a bare statement: wrapping it in &amp;lt;code&amp;gt;print&amp;lt;/code&amp;gt; would display that number right after the select tag.&lt;br /&gt;
* '''&amp;lt;code&amp;gt;master.inc.php&amp;lt;/code&amp;gt;, not &amp;lt;code&amp;gt;main.inc.php&amp;lt;/code&amp;gt;''' for the CLI script. The latter expects an HTTP session and, in pure CLI, exits silently — no output, no log, return code 0 — inside its redirect to the login page. This gives the misleading impression that the script &amp;quot;does nothing&amp;quot; rather than a real error.&lt;br /&gt;
* '''Core libraries''' — &amp;lt;code&amp;gt;master.inc.php&amp;lt;/code&amp;gt; does not load everything &amp;lt;code&amp;gt;main.inc.php&amp;lt;/code&amp;gt; loads for the web (e.g. &amp;lt;code&amp;gt;core/lib/date.lib.php&amp;lt;/code&amp;gt;, required by &amp;lt;code&amp;gt;BookKeeping&amp;lt;/code&amp;gt;). Include them explicitly if the CLI script evolves.&lt;br /&gt;
* '''The &amp;quot;nothing selected&amp;quot; option is &amp;lt;code&amp;gt;-1&amp;lt;/code&amp;gt;''' in accounting account selectors — see the Configuration section.&lt;br /&gt;
&lt;br /&gt;
== 14. Known limitations ==&lt;br /&gt;
&lt;br /&gt;
* '''Zero-decimal currencies''' (JPY, KRW…) are not handled: amounts are divided by 100 assuming two decimals. Intended use: EUR.&lt;br /&gt;
* '''The 35-day window''' is not configurable. A sync interrupted for longer must be caught up manually.&lt;br /&gt;
* '''Refund matching''' — relies on &amp;lt;code&amp;gt;ext_payment_id&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;ext_payment_site&amp;lt;/code&amp;gt; in &amp;lt;code&amp;gt;llx_paiement&amp;lt;/code&amp;gt;, filled in by the standard Stripe payment flow. A payment recorded any other way cannot be matched automatically. Matching goes through the '''PaymentIntent id''', never the charge id: the core Stripe module never stores the latter ''(it stores either the raw PaymentIntent, or a composite value, depending on the payment path taken)''.&lt;br /&gt;
* '''CSV import assumes French notation''': an export with a decimal point would not be parsed correctly.&lt;br /&gt;
* '''The &amp;quot;Transfer&amp;quot; column of the Payments CSV''' is stored for information only: it is not used to check that the sum of payments matches the imported payout amount.&lt;br /&gt;
* '''CSV-imported refunds always land in &amp;lt;code&amp;gt;error&amp;lt;/code&amp;gt;''' — intended behaviour, not a bug.&lt;br /&gt;
* '''&amp;lt;code&amp;gt;french&amp;lt;/code&amp;gt; VAT mode''' — the Stripe account's bank balance does not reflect the fees, see section 5.&lt;br /&gt;
&lt;br /&gt;
[[Category:Dolibarr]]&lt;br /&gt;
[[Category:Modules]]&lt;br /&gt;
[[Category:Accounting]]&lt;/div&gt;</summary>
		<author><name>Goldron</name></author>	</entry>

	</feed>