Turning Solidus into a full stack car dealership and rental platform
TL;DR
Difficulty: Advanced
- A used car is a Solidus product with one variant and a count of exactly one at its branch, backorders off. Each branch is a stock location.
- Facts about the car are properties, choices are option types, and anything you filter by number (the odometer reading) needs a real column added through an override.
- Rental availability is a calendar, not a stock count: a PostgreSQL daterange with an exclusion constraint stops double bookings in the database itself.
- Rental prices come from a custom price selector that reads the dates, and a deposit is a card authorisation captured in part or voided on return.
- Irish rules to design for: 14 day cancellation on cars sold at a distance, no cancellation right on fixed date car rentals, CCPC authorisation for credit intermediaries, and the VAT margin scheme at 23/123 of the margin.
A car is one of a kind, costs thousands, is paid for with deposits and finance, and if you rent it out, it is booked by date rather than sold from a shelf. This article shows how each of those awkward facts maps onto Solidus, step by step, using an invented dealership with branches in Cork and Limerick, and links to the real dealership platform I built on it.
A car is an awkward thing to sell through a shop
Most online shops sell things in boxes. Twenty identical mugs sit on a shelf, a customer buys two, 18 remain, and a parcel goes out. Every eCommerce platform is built around that picture.
A car breaks almost every part of it. A used car is one of a kind. It is worth thousands of euro, so people pay a deposit first, and many pay through finance. It sits at one branch and is collected or driven to the buyer. If the same business also rents cars out, the question is no longer "how many are in stock" but "is this car free from Friday to Monday". And around every sale and every rental sits paperwork: a licence check, a contract, a registration certificate.
Solidus is an open source eCommerce platform written in Ruby on Rails.1 It suits this kind of business because almost every piece of it can be replaced or extended in plain Ruby. Below, each awkward part of a dealership maps onto Solidus in turn, ending with the Irish rules that shape the build.
I have built this for real. The auto dealership platform case study describes it: one Ruby on Rails and Solidus platform, live behind Auto Prammer, that runs car sales, rentals, the garage, vouchers and marketing from one admin. Everything below is the general method, explained with an invented dealership, not the client's set up.
Worked example: an invented dealership in Cork
Shandon Motors is invented. Aoife runs it from two branches, one in Cork and one in Limerick. She sells used cars and rents out a small fleet of hatchbacks, and her shop lives at shandonmotors.example.
One note on units before the numbers start. Every distance in this example is in kilometres, converted exactly from miles (one mile is 1.609344 km) and rounded to the whole kilometre.
Three jobs have to run on one system:
- Selling used cars, each one unique, priced between about €8,000 and €25,000.
- Renting fleet cars by the day, collected and returned at a branch.
- Holding a security deposit on every rental, and releasing it, or part of it, when the car comes back.
A used car is a product with exactly one unit
Solidus describes what you sell in two layers. A product is the thing on the page: its name, description and images. A variant is the version a customer can actually buy, with its own price, its own stock and its own SKU (Stock Keeping Unit, the internal reference code). Every product has a master variant, and can have more variants when there are choices to make.2
Stock lives one level lower. For every variant and every stock location there is a stock item, and the stock item holds the count on hand. Solidus refuses a negative count unless the stock item is marked as backorderable, and it changes the count under a database lock, so two checkouts cannot both take the last unit.3 When inventory tracking is on, Solidus only treats a variant as in stock if one of its stock items has a count above zero.4
For a used car, this means: one product per car, one master variant, a count of exactly one at the branch where the car stands, and backorders switched off. Selling the car takes the count to zero, and it drops out of everything that lists available stock.
Worked example: the blue Golf in Cork
Aoife buys a 2021 Volkswagen Golf and puts it on the forecourt in Cork.
- She creates a product called "2021 Volkswagen Golf Life 1.5 TSI, blue", priced at €16,495.
- Solidus creates the master variant with it. She gives it the SKU
SM-0412. - At the Cork stock location the stock item gets a count on hand of 1, backorderable off. At Limerick the count is 0.
- A buyer pays the deposit online. Checkout completes and the Cork count goes from 1 to 0.
- A second buyer opens the same page a minute later. The variant now has no stock item above zero, so the car no longer counts as in stock, and there is no unit left to allocate to a second order.
Nothing here needed custom code. The stock model already understands "one of one", as long as backorders stay off.
Properties describe the car, option types describe a choice
An option type is a choice the customer makes that creates separate variants. The Solidus source describes it with the classic example: an option type such as size, with values such as small, medium and large.5 Each combination of values becomes its own variant, with its own price and stock.
A property is a plain fact about the product: a name and a value, such as "Fuel: petrol". It creates nothing. It is shown on the page. Products also sit in taxons, which are categories, such as "Make, then Volkswagen" or "Body style, then hatchback".2
So for a used car there is usually nothing to choose, and therefore no option types. The odometer reading, fuel, gearbox and colour are properties. The make and the body style are taxons, which gives the shop its category pages and filters.
Rental is where option types earn their keep. A customer does not rent "the Yaris with fleet number 7". They rent "a small automatic", and they choose how many kilometres they want.
Worked example: the small automatic rental
Aoife sets up her rental range like this:
- One product, "Small automatic hatchback", in the taxon "Rental, then Small".
- One option type, "Distance", with two values: "161 km a day" and "Unlimited".
- That gives two variants. The 161 km variant has a base rate of €42 a day. The unlimited variant has a base rate of €51 a day.
- Properties on the product: "Seats: 5", "Gearbox: automatic", "Fuel: hybrid".
Once the customer picks a distance value, Solidus knows which variant, and which base rate, applies. The fleet cars behind it live in the rental calendar.
Vehicle data that you filter by needs real columns
Properties have one weakness. Their values are text. That is fine for "Fuel: petrol". It goes wrong as soon as you sort or filter by a number.
Text is compared character by character, from the left. So the text "15,772" counts as larger than "100,000". Both start with 1, and at the second character 5 comes after 0. A filter for "under 100,000 km" built on property text would hide a car with 15,772 km on the clock, which is exactly the car a buyer wants to see.
The fix is to give the numbers you filter by real database columns. In Solidus, the recommended way to change a core class such as the product is an override: a small module that you prepend to the class, so your code runs first and can add to it.6 Put the vehicle data in its own table and attach it to the product through an override. The core tables stay untouched, which keeps upgrades simpler.
Worked example: a vehicles table for Shandon Motors
The migration creates one row per car, linked to its product:
create_table :shandon_vehicles do |t|
t.references :spree_product, null: false, foreign_key: true, index: { unique: true }
t.string :vin, null: false
t.date :first_registered_on, null: false
t.integer :odometer_km, null: false
t.string :fuel, null: false
t.string :transmission, null: false
t.timestamps
end
The override gives every product its vehicle:
module Shandon
module Spree
module Product
module AddVehicle
def self.prepended(base)
base.has_one :vehicle, class_name: "Shandon::Vehicle", foreign_key: :spree_product_id
end
::Spree::Product.prepend self
end
end
end
end
Now the filter is a number comparison:
Spree::Product.joins(:vehicle).where(shandon_vehicles: { odometer_km: ...100_000 })
Take three cars with 15,772, 50,212 and 83,686 km on the clock (9,800, 31,200 and 52,000 miles, converted exactly). As text, an "under 100,000" filter keeps none of them, because "15,772", "50,212" and "83,686" all sort after "100,000". As integers, it keeps all three, which is correct. The 15,772 km car, the one a buyer most wants, only shows up with the real column.
Each branch is a stock location
A stock location in Solidus is a named place that stock is supplied from, with its own address. Each one has its own stock items, and shipping methods are linked to stock locations.7 For Shandon Motors, the two branches are the two stock locations.
When an order moves to the delivery step, Solidus runs a stock coordinator. It filters the stock locations (by default it keeps only the active ones), sorts them, allocates the units, builds packages and then works out shipping rates for each package from the shipping methods available.8 For a dealership, a "shipping method" mostly means "how does the buyer get the car": collection from the branch, or delivery by a driver.
Worked example: a Limerick buyer wants the Cork Golf
Before the sale goes through, a buyer in Limerick asks about the blue Golf.
- The Golf's stock item in Cork holds 1. In Limerick it holds 0.
- Aoife offers two shipping methods. "Collect from the branch" costs €0 and is linked to both branches. "Delivery by our driver within 80 km" costs €149 and is also linked to both. Both prices are this example's assumptions.
- The buyer prefers to collect in Limerick. Aoife's team drives the car over.
- In the admin, they reduce the Cork count by one and raise the Limerick count by one. Cork now shows 0 and Limerick shows 1.
- The buyer checks out. The coordinator finds the unit in Limerick, and the "Collect from the branch" rate is €0.
Order, stock and collection point now agree, with no spreadsheet of which car stands where.
Rental availability is a calendar, not a stock count
A stock count answers "how many can I sell now". A rental needs to answer "is this particular car free between two dates", and the same car is free again the day it comes back. Solidus does not have a booking calendar, so this is the part you build.
The cleanest place for the rule is the database itself. PostgreSQL has range types, such as daterange, and exclusion constraints. An exclusion constraint can say "no two rows may have overlapping ranges", and with the btree_gist extension it can add "for the same car".9 Rails supports both in migrations: a daterange column and an exclusion_constraint on the table.10
One detail makes this work for rentals. A daterange includes its first day and excludes its last day. PostgreSQL writes this as [).9 So a booking from 14 to 17 August blocks the nights of the 14th, 15th and 16th, and a new booking may start on the 17th. Back to back rentals are grand, overlapping ones are refused, and the refusal comes from the database even if two people press "book" in the same second.
The flow runs from the customer's choice to one of two outcomes: the dates are held on a specific car, or every car in the class is taken. The decision is the important box. The check is the insert itself, not a query beforehand that another customer could beat by a second.
Worked example: fleet car 7 in August
Fleet car 7 is a small automatic in Cork. The bookings table has a car reference and a daterange called period:
enable_extension "btree_gist"
create_table :shandon_bookings do |t|
t.references :shandon_fleet_car, null: false, foreign_key: true
t.daterange :period, null: false
t.exclusion_constraint "shandon_fleet_car_id WITH =, period WITH &&", using: :gist
t.timestamps
end
Three customers try to book it.
- Booking A, collect Friday 14 August 2026, return Monday 17 August. Stored as
[2026-08-14, 2026-08-17). Accepted. - Booking B, Sunday 16 August to Tuesday 18 August. Stored as
[2026-08-16, 2026-08-18). The 16th is inside booking A, so the ranges overlap and PostgreSQL rejects the row. The system moves on and tries fleet car 8. - Booking C, Monday 17 August to Wednesday 19 August. Stored as
[2026-08-17, 2026-08-19). Booking A excludes the 17th, so there is no overlap. Accepted, and car 7 goes straight back out.
For a cleaning day between rentals, store each blocked period one day longer than the rental.
Rental pricing is a sum of daily rates
A rental price is not one number from the product page. It depends on how many days, which days, which season, and whether a long rental earns a discount. Written as a formula:
Here is the rental price, is the number of rental days, counts the days from to , is the base daily rate of the variant, is the weekend factor for day (for example on a Saturday or Sunday, otherwise), is the season factor for day (for example in August, otherwise) and is the duration discount as a share, for example when is seven or more and below that. The discount is a percentage taken off the total, see Foundations: percentages.
Where does this live in Solidus? Two facts decide it.
First, a line item gets its price from the variant's price selector, which picks a price for a given set of pricing options. By default those options are only the currency and the country.11 The pricing options for a line item are built by a class method called from_line_item, and both the selector and its options class are configurable.1213
Second, the browser cannot send a price. The default permitted line item attributes are the id, the variant, the quantity and a customer_metadata hash, nothing else.14 And for stores on the Solidus 5.0 defaults, a setting called recalculate_cart_prices is on, which recalculates cart prices from the price selector whenever the order is recalculated.13 A price written onto the line item by hand would be overwritten.
So the rental dates go into the line item's customer_metadata. A custom pricing options class reads them in from_line_item. A custom price selector applies the formula and returns the price. Because the price comes from the selector, every recalculation gives the same answer. One more setting keeps two rentals of the same variant on different dates apart: Solidus lets you add comparison hooks, methods on the order that decide whether a new item is the same as an existing line item.13
Worked example: seven days in August
A customer books the small automatic, 161 km a day, from Friday 14 August to Friday 21 August 2026. That is days: Friday, Saturday, Sunday, Monday, Tuesday, Wednesday, Thursday. The base rate is . All seven days are in August, so every day. Saturday and Sunday have . These factors are this example's assumptions, chosen to show the arithmetic.
- The weekday rate: .
- The weekend rate: .
- Add the seven days, five weekdays and two weekend days: .
- The duration discount. Seven days earns , so .
The customer pays €340.20. In this example the rates include VAT. For comparison, the three day weekend from Friday to Monday is , with no discount, because three is below seven.
The horizontal axis shows the seven rental days, the vertical axis the rate for each day in euro, before the 10% discount comes off the total. The takeaway is that the weekend days are where the money is made, and the seven bars added up give the €378.00 that the discount then reduces to €340.20.
A deposit is an authorisation, captured later
Card payments have two steps. Authorising reserves the money on the customer's card. Capturing actually takes it. Solidus is built around this split: its configuration notes that authorising and capturing later handles errors far better, and auto_capture is off by default.13 When an order's payments are processed, each one is either authorised or authorised and captured in one go, depending on the payment method. An authorised payment is captured later, or voided if it is not needed.15 The capture! method takes an amount in cent, so you can capture less than you authorised.16
Stripe works the same way, and a partial capture automatically releases the rest. There is a limit: an authorisation for an online card payment is usually valid for seven days, and a payment not captured in time is cancelled and the money released.17 Holds taken on a card terminal in person are shorter, two days for most card brands.17 For a rental, that window decides when you take the hold: close to collection for short rentals, and Stripe's extended authorisation or a different deposit method for long ones.
The rental price is a normal payment, captured at booking. The deposit is a second payment, on a method with auto capture off.
Worked example: the weekend deposit
A customer rents the small automatic for the weekend, Friday 14 to Monday 17 August. Aoife's security deposit is €250, an assumption for this example.
- At booking, the customer pays the rental price of €176.40. It is captured straight away.
- On Friday morning, the collection email asks the customer to authorise €250 online, on the same card. In Solidus it sits as a pending payment. Nothing has left the customer's account yet. The hold is valid for seven days, and the car is due back in three.
- On Monday the car comes back a quarter of a tank short. Aoife's refuelling charge is €38, again an assumption.
- Her team captures €38 of the €250. In cent, that is
capture!(3800). - Stripe releases the other €212 to the customer: €250 minus €38.
- Had the car come back full and undamaged, the team would void the authorisation and the whole €250 would be released.
The customer only ever pays for what actually happened, and nobody has to process a refund.
A car checkout needs different steps
Out of the box, a Solidus checkout runs: cart, address, delivery, payment if needed, confirm, complete. You change it with an override on the order, using remove_checkout_step and insert_checkout_step with a before or after position.18 A step can also carry a condition, so it only applies to some orders.19
The Solidus guides carry two warnings worth knowing before you remove anything. Remove the address step but keep delivery, and delivery breaks, because it expects an address. And the stock coordinator from earlier runs when the order enters delivery, so if you remove that step, you must call it yourself.188
For Shandon Motors, the rental checkout needs one thing a parcel checkout does not: a driving licence check before payment.
Worked example: a licence step for rentals only
The override adds one step, only for orders that contain a rental:
module Shandon
module Spree
module Order
module AddLicenceStep
def self.prepended(base)
base.insert_checkout_step :licence, before: :payment, if: ->(order) { order.rental? }
end
::Spree::Order.prepend self
end
end
end
end
The rental? method is Shandon's own: true when any line item belongs to the rental taxon. A rental now runs cart, address, delivery, licence, payment, confirm, complete. A used car sale skips the licence step and runs as before. The step needs its own view, a form for the licence details, which the Solidus guide covers next to the code.18
Documents belong on the record they prove
A dealership produces paperwork at every turn: the licence check for a rental, the rental contract, the sales invoice, photos of the car at collection and return. Rails has Active Storage for this. It attaches files to any record with has_one_attached or has_many_attached, and stores them on disk or in a cloud bucket.20
The licence check works differently in Ireland. The UK has an online check code a driver can share with a hire company, but that service only covers licences issued in England, Wales or Scotland.21 An Irish licence has no share code of that kind, so an Irish rental firm checks the licence itself. Every EU licence follows one model with numbered fields, among them the date of expiry of the licence (field 4b), the categories the holder may drive (field 9) and the date of expiry of each category (field 11).22
Keep only what you need: the licence number, the expiry dates and the result. A scan of the licence is personal data you must protect and later delete, the theme of treating privacy as a strength.
Worked example: which date the licence has to cover
A customer books a rental on 10 July for collection on 14 August and return on 17 August. At checkout they enter the date in field 4b of their licence: 12 August 2026. On 10 July the licence is valid, so a check against the booking date would pass. By collection it has run out.
So the licence step compares the expiry dates with the return date, not the booking date:
- At checkout, the licence step records the licence number, the expiry date in field 4b, the expiry date of category B in field 11, and the driver's consent.
- It compares both dates with 17 August. 12 August is earlier, so the booking is flagged, and an email tells the customer that the licence has to be valid until the car comes back.
- At collection, a member of staff checks the card against the record, marks the booking "licence checked" with the date, and the result is stored on the booking.
- The signed rental contract is generated as a PDF and attached to the booking with
has_one_attached :contract, together with the photos taken at handover.
If a dispute comes up in October, it is all on one record.
The Irish rules that shape the build
A few Irish rules change how the software must behave. In brief, and not as legal advice:
Cancellation for cars sold online. Under the Consumer Rights Act 2022, when a consumer buys goods at a distance, the cancellation period ends 14 days after the day the consumer acquires physical possession of the goods.23 The rule comes from the EU Consumer Rights Directive, Article 9 of Directive 2011/83/EU.24 For a car sold online and delivered, the clock starts when the buyer gets the car, so any deposit and payment logic must allow for a refund in that window.
Rentals with fixed dates. The same Act excludes car rental services from the cancellation right where the contract provides for a specific date or period of performance.25 The Directive has the same exception in Article 16(l).24 A rental for 14 to 17 August is such a contract. Your own cancellation terms still apply, so the shop must say clearly what they are.
Finance. A dealer who arranges credit for a customer in return for a commission from the lender is a credit intermediary under the Consumer Credit Act 1995, and garages are the CCPC's own example. The CCPC (Competition and Consumer Protection Commission) authorises credit intermediaries and keeps their register, and it shares responsibility for credit advertising with the Central Bank of Ireland. Credit adverts must show the APR, as prominently as any other figure.26 The Central Bank itself authorises the firms that provide the credit or enter into hire purchase agreements with consumers.27 A finance calculator on a car page is an advert, not a design detail.
VAT on used cars. Many used cars are sold under the VAT margin scheme, where VAT is due on the margin, not on the full price. The scheme covers goods a dealer bought from someone who could not issue a VAT invoice, such as a private individual, so a vehicle bought on an invoice that shows VAT separately is not eligible. Revenue treats the margin as an amount that already includes VAT.28 The margin and the VAT come from two steps:
Here is the gross margin, is the selling price, is the purchase price, and is the VAT fraction for the Irish standard rate of 23%.29 Revenue's own worked example uses the same fraction.28 It is the share of a price that includes 23% VAT, because , see Foundations: percentages.
Worked example: VAT on the blue Golf
Aoife bought the Golf from a private seller for €14,200, so there was no VAT invoice and the car qualifies. She sells it for €16,495.
- The margin: .
- Multiply by 23: .
- Divide by 123: , which rounds to .
- For comparison, VAT on the full price: , and .
She owes €429.15 of VAT on this sale, not the €3,084.43 it would be if VAT were due on the full price. The UK edition of this example comes to a different VAT figure from the same margin, because the standard rate there is different. Solidus's default tax calculation works from tax categories, tax rates and rate calculators, and its guide shows two ways to change it: a custom rate calculator, or replacing the tax calculator altogether.30 A margin scheme car needs one of those, fed with the purchase price from the vehicle record, or the VAT has to be worked out outside the order. Decide which before the first car goes live.
Where to start
Each piece is a small extension of the same Solidus core, not a rebuild. A sensible order follows the risk:
- The vehicle data and the one unit stock rule, because everything else reads from them.
- The rental calendar with its exclusion constraint, because a double booking is the most expensive mistake.
- Rental pricing in a price selector, so every recalculation agrees.
- The deposit as an authorisation, with the hold window in mind.
- The checkout steps and the documents.
Email automation and tracking are their own topics: sending order emails safely is in Postmark with Rails and Solidus, and consent aware tracking is in event tracking and consent mode. To see the whole thing running a real business, the auto dealership platform case study shows sales, rentals, the garage, vouchers and marketing working from one admin.
Footnotes
-
Solidus, Solidus README, GitHub, 2026, https://github.com/solidusio/solidus
-
Solidus, core/app/models/spree/product.rb, GitHub, 2026, https://github.com/solidusio/solidus/blob/main/core/app/models/spree/product.rb
-
Solidus, core/app/models/spree/stock_item.rb, GitHub, 2026, https://github.com/solidusio/solidus/blob/main/core/app/models/spree/stock_item.rb
-
Solidus, core/app/models/spree/variant.rb, GitHub, 2026, https://github.com/solidusio/solidus/blob/main/core/app/models/spree/variant.rb
-
Solidus, core/app/models/spree/option_type.rb, GitHub, 2026, https://github.com/solidusio/solidus/blob/main/core/app/models/spree/option_type.rb
-
Solidus, Customizing the core, Solidus Guides, 2026, https://guides.solidus.io/customization/customizing-the-core/
-
Solidus, core/app/models/spree/stock_location.rb, GitHub, 2026, https://github.com/solidusio/solidus/blob/main/core/app/models/spree/stock_location.rb
-
Solidus, Stock management, Solidus Guides, 2026, https://guides.solidus.io/advanced-solidus/stock-and-fulfillment/
-
The PostgreSQL Global Development Group, Range Types (section 8.17), PostgreSQL Documentation, 2026, https://www.postgresql.org/docs/current/rangetypes.html
-
Ruby on Rails, Active Record and PostgreSQL, Rails Guides, 2026, https://guides.rubyonrails.org/active_record_postgresql.html
-
Solidus, core/app/models/spree/variant/price_selector.rb, GitHub, 2026, https://github.com/solidusio/solidus/blob/main/core/app/models/spree/variant/price_selector.rb
-
Solidus, core/app/models/spree/variant/pricing_options.rb, GitHub, 2026, https://github.com/solidusio/solidus/blob/main/core/app/models/spree/variant/pricing_options.rb
-
Solidus, core/lib/spree/app_configuration.rb, GitHub, 2026, https://github.com/solidusio/solidus/blob/main/core/lib/spree/app_configuration.rb
-
Solidus, core/lib/spree/permitted_attributes.rb, GitHub, 2026, https://github.com/solidusio/solidus/blob/main/core/lib/spree/permitted_attributes.rb
-
Solidus, Payments and refunds, Solidus Guides, 2026, https://guides.solidus.io/advanced-solidus/payments-and-refunds/
-
Solidus, core/app/models/spree/payment/processing.rb, GitHub, 2026, https://github.com/solidusio/solidus/blob/main/core/app/models/spree/payment/processing.rb
-
Stripe, Place a hold on a payment method, Stripe Documentation, 2026, https://docs.stripe.com/payments/place-a-hold-on-a-payment-method
-
Solidus, Redefining checkout steps, Solidus Guides, 2026, https://guides.solidus.io/cookbook/redefining-checkout-steps/
-
Solidus, core/app/models/spree/core/state_machines/order/class_methods.rb, GitHub, 2026, https://github.com/solidusio/solidus/blob/main/core/app/models/spree/core/state_machines/order/class_methods.rb
-
Ruby on Rails, Active Storage Overview, Rails Guides, 2026, https://guides.rubyonrails.org/active_storage_overview.html
-
Driver and Vehicle Licensing Agency, View or share your driving licence information, GOV.UK, 2026, https://www.gov.uk/view-driving-licence
-
European Parliament and Council, Directive 2006/126/EC on driving licences, Annex I, EUR-Lex, 2006, https://eur-lex.europa.eu/legal-content/EN/TXT/HTML/?uri=CELEX:32006L0126
-
Government of Ireland, Consumer Rights Act 2022, section 113, Irish Statute Book, 2022, https://www.irishstatutebook.ie/eli/2022/act/37/section/113/enacted/en/html
-
European Parliament and Council, Directive 2011/83/EU on consumer rights, Articles 9 and 16, EUR-Lex, 2011, https://eur-lex.europa.eu/legal-content/EN/TXT/HTML/?uri=CELEX:32011L0083
-
Government of Ireland, Consumer Rights Act 2022, section 111, Irish Statute Book, 2022, https://www.irishstatutebook.ie/eli/2022/act/37/section/111/enacted/en/html
-
Competition and Consumer Protection Commission, Credit Intermediaries, CCPC, 2026, https://www.ccpc.ie/enforcement-and-regulation/registers/credit-intermediaries
-
Central Bank of Ireland, Retail Credit and Home Reversion Firms, Central Bank of Ireland, 2026, https://www.centralbank.ie/regulation/industry-market-sectors/retail-credit-home-reversion-firms
-
Revenue, Supplies of second hand goods (Tax and Duty Manual), Revenue, 2026, https://www.revenue.ie/en/tax-professionals/tdm/value-added-tax/part03-taxable-transactions-goods-ica-services/Goods/goods-second-hand-goods.pdf
-
Revenue, Current VAT rates, Revenue, 2026, https://www.revenue.ie/en/vat/vat-rates/search-vat-rates/current-vat-rates.aspx
-
Solidus, Taxation, Solidus Guides, 2026, https://guides.solidus.io/advanced-solidus/tax-calculation/