{"id":404700,"date":"2026-11-14T19:40:51","date_gmt":"2026-11-14T19:40:51","guid":{"rendered":"https:\/\/wordpress.org\/plugins\/dahuconcept-deferred-sepa-for-woocommerce\/"},"modified":"2026-11-14T19:40:37","modified_gmt":"2026-11-14T19:40:37","slug":"dahuconcept-deferred-sepa","status":"publish","type":"plugin","link":"https:\/\/wordpress.org\/plugins\/dahuconcept-deferred-sepa\/","author":17844309,"comment_status":"closed","ping_status":"closed","template":"","meta":{"version":"1.2.5","stable_tag":"1.2.5","tested":"7.1.4","requires":"6.2","requires_php":"7.4","requires_plugins":null,"header_name":"DahuConcept Deferred SEPA for WooCommerce","header_author":"Hugo Vial-Jaime","header_description":"Charges a Stripe SEPA Direct Debit mandate a configurable number of days after an order is completed, instead of at checkout. The charge date can be viewed and moved from the order screen.","last_updated":"2026-11-14 19:40:37","external_support_url":"","external_repository_url":"","donate_link":"","header_plugin_uri":"","header_author_uri":"https:\/\/dahu-concept.fr","rating":0,"author_block_rating":0,"active_installs":0,"downloads":50,"num_ratings":0,"support_threads":0,"support_threads_resolved":0,"author_block_count":0,"sections":["description","installation","faq","changelog"],"tags":{"1.2.5":{"tag":"1.2.5","author":"dahuconcept","date":"2026-11-14 19:40:37","revision":3802411}},"upgrade_notice":{"1.2.4":"<p>French now works on a copy installed by hand: the compiled catalogue travels in the package. The French readme moves under docs\/.<\/p>","1.2.3":"<p>WordPress 6.2 is now the floor, in step with the rest of the family. Nothing else changes.<\/p>","1.1.1":"<p>Fixes a per-customer charge delay of zero days being discarded, and completes the uninstaller.<\/p>","1.1.0":"<p>Reports charges that came back after being collected, and unsticks orders whose\ndebit was refused before settlement.<\/p>","1.0.0":"<p>First release.<\/p>"},"ratings":[],"assets_icons":{"icon-128x128.png":{"filename":"icon-128x128.png","revision":3802409,"resolution":"128x128","location":"assets","locale":"","width":128,"height":128},"icon-256x256.png":{"filename":"icon-256x256.png","revision":3802409,"resolution":"256x256","location":"assets","locale":"","width":256,"height":256},"icon.svg":{"filename":"icon.svg","revision":3802409,"resolution":false,"location":"assets","locale":false}},"assets_banners":{"banner-1544x500.png":{"filename":"banner-1544x500.png","revision":3802409,"resolution":"1544x500","location":"assets","locale":"","width":1544,"height":500},"banner-772x250.png":{"filename":"banner-772x250.png","revision":3802409,"resolution":"772x250","location":"assets","locale":"","width":772,"height":250}},"assets_blueprints":{},"all_blocks":[],"tagged_versions":["1.2.5"],"block_files":[],"assets_screenshots":{"screenshot-1.png":{"filename":"screenshot-1.png","revision":3802409,"resolution":"1","location":"assets","locale":"","width":1280,"height":1544},"screenshot-2.png":{"filename":"screenshot-2.png","revision":3802409,"resolution":"2","location":"assets","locale":"","width":1100,"height":329},"screenshot-3.png":{"filename":"screenshot-3.png","revision":3802409,"resolution":"3","location":"assets","locale":"","width":1120,"height":496},"screenshot-4.png":{"filename":"screenshot-4.png","revision":3802409,"resolution":"4","location":"assets","locale":"","width":1280,"height":767}},"screenshots":{"1":"The gateway settings.","2":"Naming a customer's active mandate on their user profile.","3":"The charge date on the order screen, with the button to send it now.","4":"WooCommerce \u2192 SEPA charges: expected cash, and the history."}},"plugin_section":[],"plugin_tags":[74137,25540,274720,281557,33609],"plugin_category":[],"plugin_contributors":[292004],"plugin_business_model":[],"class_list":["post-404700","plugin","type-plugin","status-publish","hentry","plugin_tags-deferred-payment","plugin_tags-direct-debit","plugin_tags-mandate","plugin_tags-payment-terms","plugin_tags-sepa","plugin_contributors-dahuconcept","plugin_committers-dahuconcept"],"banners":{"banner":"https:\/\/ps.w.org\/dahuconcept-deferred-sepa\/assets\/banner-772x250.png?rev=3802409","banner_2x":"https:\/\/ps.w.org\/dahuconcept-deferred-sepa\/assets\/banner-1544x500.png?rev=3802409","banner_rtl":false,"banner_2x_rtl":false},"icons":{"svg":"https:\/\/ps.w.org\/dahuconcept-deferred-sepa\/assets\/icon.svg?rev=3802409","icon":"https:\/\/ps.w.org\/dahuconcept-deferred-sepa\/assets\/icon.svg?rev=3802409","icon_2x":false,"generated":false},"screenshots":[{"src":"https:\/\/ps.w.org\/dahuconcept-deferred-sepa\/assets\/screenshot-1.png?rev=3802409","caption":"The gateway settings."},{"src":"https:\/\/ps.w.org\/dahuconcept-deferred-sepa\/assets\/screenshot-2.png?rev=3802409","caption":"Naming a customer's active mandate on their user profile."},{"src":"https:\/\/ps.w.org\/dahuconcept-deferred-sepa\/assets\/screenshot-3.png?rev=3802409","caption":"The charge date on the order screen, with the button to send it now."},{"src":"https:\/\/ps.w.org\/dahuconcept-deferred-sepa\/assets\/screenshot-4.png?rev=3802409","caption":"WooCommerce \u2192 SEPA charges: expected cash, and the history."}],"raw_content":"<!--section=description-->\n<p>Some customers are not charged when they order. A trade account on 30-day terms,\na wholesale buyer, a subscription-style arrangement: the mandate is signed, the\norder ships, and the money is taken later.<\/p>\n\n<p>Stripe charges a SEPA Direct Debit as soon as the order is placed. This plugin\nadds a checkout option that does not. It reuses the mandate the customer has\nalready saved, records it on the order, puts the order on hold, and schedules\nthe real charge for a configurable number of days after the order is marked\nCompleted. Eight days by default, overridable per customer.<\/p>\n\n<p><strong>Who may pay late is decided by the shop, not by the customer.<\/strong> An\nadministrator names one of the customer's saved IBANs as the active mandate on\ntheir user profile. The customer must also have that same IBAN set as their own\ndefault. Both halves have to agree, which is what stops a customer quietly\nswitching to an IBAN nobody approved.<\/p>\n\n<h4>What it does<\/h4>\n\n<ul>\n<li><strong>Deferred charging.<\/strong> Nothing is taken at checkout. The charge is scheduled\nautomatically when the order is completed, using Action Scheduler.<\/li>\n<li><strong>Move the date.<\/strong> The due date is shown on the order screen and can be moved\nuntil the moment it fires, with every move recorded.<\/li>\n<li><strong>Charge now.<\/strong> A button to send the charge immediately, behind a capability\ncheck, a nonce and a confirmation.<\/li>\n<li><strong>Charged once, whatever happens.<\/strong> A persistent idempotency key per order\nmeans a replayed scheduled action, a network timeout or a retry can never\nproduce a second debit.<\/li>\n<li><strong>Its own Stripe webhook.<\/strong> SEPA is asynchronous: a charge leaves as\n\"processing\" and succeeds or fails days later. A dedicated endpoint with its\nown signing secret listens for the outcome and for disputes, independently of\nthe official Stripe plugin's webhook.<\/li>\n<li><strong>Per-customer terms.<\/strong> A delay set on a customer's profile overrides the\nglobal one, for a trade account on different terms.<\/li>\n<li><strong>A charges screen.<\/strong> WooCommerce \u2192 SEPA charges, with an Upcoming tab for\nexpected cash and a History tab of every charge sent, filterable by date and\noutcome, sortable, and exportable to CSV.<\/li>\n<li><strong>Optionally hides Stripe's immediate SEPA option<\/strong> at checkout, while\nleaving it available under My account \u2192 Payment methods, where the customer\nsigns the mandate in the first place.<\/li>\n<li><strong>Nothing fails in silence.<\/strong> A refused charge is emailed to the shop, once,\nwith the reason and never with a raw carrier response. A charge whose date\nhas passed and which never ran is taken out of \"upcoming\", listed at the top\nof the screen with what is in the way, and reported by a daily check: a\ndeferred charge is the one payment path with no customer in front of a\nscreen to find out it did not work. Nothing is ever retried automatically,\nbecause a charge that may already have been sent would be taken twice.<\/li>\n<li><strong>Diagnostics.<\/strong> The gateway is deliberately hard to qualify for, so it can\nshow an administrator each condition it checks and the answer, rather than\nleaving \"why is it not showing?\" to be guessed.<\/li>\n<li><strong>HPOS and legacy order storage.<\/strong> Both, with no custom tables: everything is\norder meta.<\/li>\n<\/ul>\n\n<h4>What it needs<\/h4>\n\n<p>WooCommerce, and SEPA Direct Debit working at Stripe \u2014 in practice the official\nWooCommerce Stripe Payment Gateway, which is what lets a customer sign and save\na mandate. This plugin does not replace it and does not take card payments.<\/p>\n\n<p>The Stripe secret key can be this plugin's own setting (a restricted key that\nmay only write PaymentIntents is enough) or borrowed from the official plugin's\nconfiguration.<\/p>\n\n<h4>Before you switch it on<\/h4>\n\n<p>A SEPA mandate carries obligations to the payer that are yours, not this\nplugin's. Depending on the mandate you use, you may owe the customer advance\nnotice of the date and amount of each debit. Deferring a charge changes that\ndate. Check what your mandate and your bank require before charging customers\non a delay, and tell your customers what to expect.<\/p>\n\n<p>This plugin is not affiliated with or endorsed by Stripe.<\/p>\n\n<h3>External services<\/h3>\n\n<p>This plugin sends requests to <strong>Stripe<\/strong> (https:\/\/api.stripe.com), using the\nStripe API key you configure. Stripe is the payment processor that holds the\ncustomer's SEPA mandate and moves the money; without it the plugin has nothing\nto charge.<\/p>\n\n<p>What is sent, and when:<\/p>\n\n<ul>\n<li><strong>When a deferred charge comes due<\/strong> \u2014 the order total and currency, the\nStripe customer and payment-method identifiers already saved on the order, a\ndescription containing the order number and your site name, and metadata\ncontaining the order id, the order number and your site URL. This is the\nrequest that creates and confirms the payment.<\/li>\n<li><strong>When resolving which Stripe customer owns a saved mandate<\/strong> \u2014 the\npayment-method identifier alone, read back to confirm its owner. The answer is\ncached so this is not repeated.<\/li>\n<\/ul>\n\n<p>Nothing is sent for an order that is not being charged, and no request is made\nunless a Stripe secret key is configured.<\/p>\n\n<p>The plugin also <em>receives<\/em> requests from Stripe, at its own webhook endpoint,\ntelling it whether a charge succeeded, failed or was disputed. Those requests\nare rejected unless they carry a valid signature for your webhook signing\nsecret.<\/p>\n\n<p>Stripe's terms: https:\/\/stripe.com\/legal\/ssa\nStripe's privacy policy: https:\/\/stripe.com\/privacy<\/p>\n\n<p>This plugin is not affiliated with, endorsed by or sponsored by Stripe.<\/p>\n\n<!--section=installation-->\n<ol>\n<li>Install and activate WooCommerce, and the official WooCommerce Stripe\nPayment Gateway with SEPA Direct Debit enabled. Keep Stripe in test mode\nwhile you set this up.<\/li>\n<li>Install and activate this plugin.<\/li>\n<li>Go to WooCommerce \u2192 Settings \u2192 Payments \u2192 Deferred SEPA Direct Debit and\nenable it. Set the default delay.<\/li>\n<li><p>Create a webhook endpoint in your Stripe dashboard pointing at\n   https:\/\/yoursite.example\/wp-json\/ubac-sepa\/v1\/webhook, listening\nfor <code>payment_intent.succeeded<\/code>, <code>payment_intent.payment_failed<\/code> and\n   charge.dispute.created. Copy its signing secret into <code>wp-config.php<\/code>:<\/p>\n\n<p>define( 'UBAC_SEPA_WEBHOOK_SECRET', 'whsec_...' );<\/p><\/li>\n<li><p>Have a test customer save an IBAN under My account \u2192 Payment methods, then\napprove it on their user profile under Deferred SEPA Direct Debit.<\/p><\/li>\n<\/ol>\n\n<p>Test mode and live mode are separate endpoints with different secrets. When you\ngo live, create a live endpoint and update the constant, or every notification\nis rejected and orders stay unresolved.<\/p>\n\n<!--section=faq-->\n<dl>\n<dt id=\"why%20does%20the%20gateway%20not%20appear%20at%20checkout%3F\"><h3>Why does the gateway not appear at checkout?<\/h3><\/dt>\n<dd><p>It appears only when all of these hold: it is enabled; the customer is signed\nin; their role is not excluded; an administrator has named one of their IBANs\nas the active mandate; the customer has that same IBAN as their own default;\nand a Stripe secret key is configured. Turn on Diagnostics in the gateway\nsettings and the plugin will tell you which one is failing.<\/p><\/dd>\n<dt id=\"can%20a%20customer%20be%20charged%20twice%3F\"><h3>Can a customer be charged twice?<\/h3><\/dt>\n<dd><p>No. Each order gets one idempotency key, generated once and reused, so Stripe\ntreats every retry of the same charge as the same request.<\/p><\/dd>\n<dt id=\"what%20happens%20if%20a%20charge%20fails%3F\"><h3>What happens if a charge fails?<\/h3><\/dt>\n<dd><p>The webhook marks the order failed and records the reason Stripe gave, as an\norder note. The charge is not retried automatically: a failed direct debit\nusually needs a human to look at it.<\/p><\/dd>\n<dt id=\"does%20it%20work%20without%20the%20official%20stripe%20plugin%3F\"><h3>Does it work without the official Stripe plugin?<\/h3><\/dt>\n<dd><p>The charging does, given a secret key and a customer who already has a mandate\nsaved at Stripe. But that plugin is how a customer signs and saves a mandate in\nthe first place, so in practice you want both.<\/p><\/dd>\n<dt id=\"does%20it%20handle%20currencies%20other%20than%20the%20euro%3F\"><h3>Does it handle currencies other than the euro?<\/h3><\/dt>\n<dd><p>No. Stripe accepts SEPA Direct Debit in euro only. An order in another currency\nis refused with an order note rather than charged in the wrong unit.<\/p><\/dd>\n<dt id=\"is%20my%20order%20history%20deleted%20if%20i%20uninstall%20it%3F\"><h3>Is my order history deleted if I uninstall it?<\/h3><\/dt>\n<dd><p>No. The settings are removed and any queued charge is cancelled. Everything\nwritten on an order \u2014 when each charge was due, when it was sent, what Stripe\nanswered \u2014 is a payment record and is kept. No setting changes that: an\nuninstall never removes it, not least because an uninstall is very often a step\nin reinstalling. If you have to erase it \u2014 a data-erasure request, say \u2014 that is\nan operation on your own orders, through WooCommerce's personal data tools or\nyour database, and not something this plugin does on the way out.<\/p><\/dd>\n\n<\/dl>\n\n<!--section=changelog-->\n<h4>1.2.5<\/h4>\n\n<ul>\n<li>Changed: the requested payment method read on the order screen is now sanitised with <code>sanitize_text_field()<\/code> rather than <code>wc_clean()<\/code>. The line was already sanitising, but WPCS does not recognise WooCommerce's helper and reported it as unsanitised input \u2014 on a payment path, using the tool the tooling recognises is better than annotating the warning away. It also fails more safely: a forged array now yields an empty string, which matches no known gateway, where the previous helper returned the array.<\/li>\n<li>Changed: the two user-meta deletions at uninstall now carry the <code>SlowDBQuery<\/code> sniff in their annotation, with the reason. Deleting by meta key alone is the point there \u2014 it removes every user's copy in one pass, once \u2014 and indexing for it would cost on every request for the life of the shop.<\/li>\n<\/ul>\n\n<h4>1.2.4<\/h4>\n\n<ul>\n<li>Changed: the compiled French catalogue now travels in the package. It was left out on the grounds that the directory serves language packs, which is true only for a copy installed from the directory and only once translations have been imported: a copy installed by hand showed English with a catalogue that was fully translated.<\/li>\n<li>Changed: the French readme moves under docs\/, so the directory's checker no longer reports an unexpected markdown file at the plugin root.<\/li>\n<\/ul>\n\n<h4>1.2.3<\/h4>\n\n<ul>\n<li>Changed: WordPress 6.2 is now the floor. Three extensions of the family need it for prepare()'s %i identifier placeholder, and the family states one floor rather than two because the welcome screen and the shared admin stylesheet are copied byte for byte across it.<\/li>\n<\/ul>\n\n<h4>1.2.2<\/h4>\n\n<ul>\n<li>Fixed: a webhook event could confirm a charge on an order this plugin had never charged. The signature proves an event comes from Stripe, not that it concerns this order, and the metadata it carries is set by whoever created the payment. A confirmation now requires the payment intent this plugin recorded when it sent the charge, and the two must match.<\/li>\n<li>An event that has nothing to confirm is refused, and the refusal is written on the order rather than dropped in silence, so a charge whose Stripe answer was lost in transit can still be picked up by hand.<\/li>\n<li>Fixed: an event whose payment id arrived in Stripe's expanded form wrote the literal text \"Array\" as the order's payment reference. The id is now read one way and used everywhere, instead of being read once for the check and once again for the record.<\/li>\n<li>An event the plugin does not act on no longer writes anything on the order, and a refusal is written once per order and per event type rather than on every delivery.<\/li>\n<li>Fixed: an event is applied once however often Stripe redelivers it. Replays left the amounts alone but repeated the order notes, and a redelivered failure on a settled charge sent one reversal alert per delivery.<\/li>\n<\/ul>\n\n<h4>1.2.1<\/h4>\n\n<ul>\n<li>An empty Upcoming tab now says what will appear there instead of showing a bare line of text, and an empty History tab no longer blames filters that were never set.<\/li>\n<\/ul>\n\n<h4>1.2.0<\/h4>\n\n<ul>\n<li>The activation screen now opens on the plugin's own mark rather than its initials, and the banner carries the same drawing as a watermark.<\/li>\n<\/ul>\n\n<h4>1.1.1<\/h4>\n\n<ul>\n<li>A per-customer charge delay of zero days \u2014 \"charge as soon as it ships\" \u2014 is\nnow saved. It was silently discarded, and that customer fell back to the\nshop's global delay with nothing on screen to say so.<\/li>\n<li>Uninstalling now also removes the overdue-sweep marker, any charge claim left\nbehind by a worker killed mid-charge, and the daily sweep's own cron event,\nwhich used to keep waking the site up for a hook nothing answered.<\/li>\n<\/ul>\n\n<h4>1.1.0<\/h4>\n\n<ul>\n<li>A charge Stripe accepted and then refused before settlement no longer leaves\nthe order stuck. Nothing left the customer's account, so the order is\nreopened and can be scheduled again \u2014 previously the \"already charged\" guard\nrefused a charge, a reschedule and the \"settled another way\" mark alike,\nleaving no way out at all.<\/li>\n<li>A failure or a dispute reported on a charge that was already collected is\nemailed to the shop. It was written on the order and told to nobody.<\/li>\n<li>Such an order is deliberately left untouched: undoing a settlement on an\nevent that may be a duplicate is the one move that could cost money.<\/li>\n<\/ul>\n\n<h4>1.0.0<\/h4>\n\n<ul>\n<li>First release.<\/li>\n<\/ul>","raw_excerpt":"Charge a customer's saved SEPA mandate a set number of days after their order ships, instead of at checkout.","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin\/404700","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin"}],"about":[{"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/types\/plugin"}],"replies":[{"embeddable":true,"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/comments?post=404700"}],"author":[{"embeddable":true,"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wporg\/v1\/users\/dahuconcept"}],"wp:attachment":[{"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/media?parent=404700"}],"wp:term":[{"taxonomy":"plugin_section","embeddable":true,"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_section?post=404700"},{"taxonomy":"plugin_tags","embeddable":true,"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_tags?post=404700"},{"taxonomy":"plugin_category","embeddable":true,"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_category?post=404700"},{"taxonomy":"plugin_contributors","embeddable":true,"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_contributors?post=404700"},{"taxonomy":"plugin_business_model","embeddable":true,"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_business_model?post=404700"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}