Skip to main content

Payment in person

A sale at the counter is a second payment path beside the remote one. A card reader stands at a location, the buyer presents a card, and the sale is taxed in the country the reader stands in. The package drives the reader from your server through Stripe Terminal's server-driven integration, so there is no code to run on the reader or in a browser.

The path is the CardPresentPayments contract. The Stripe driver binds it. The Mollie driver binds nothing, because its terminals are not wired, so ask the container before you offer a sale at the counter:

use Pushery\Billing\Contracts\CardPresentPayments;

if (app()->bound(CardPresentPayments::class)) {
// offer the counter
}

Pairing a reader​

A reader belongs to a Stripe Terminal location, and the location's address is where every sale on the reader is placed. Create the location in the Stripe dashboard or through the API, then pair the reader with the code it shows on its screen:

$reader = app(CardPresentPayments::class)->pairReader('purple-dog-cat', 'tml_123', label: 'Till 1');

$reader->country; // 'DE', from the location's address
$reader->canTakeASale(); // online, not busy, and standing somewhere with a country

reader() asks the provider for the reader's current state. online says whether the provider can reach it, busy whether it is taking a payment right now.

Putting a sale on a reader​

use Pushery\Billing\Enums\TaxArchetype;
use Pushery\Billing\ValueObjects\InPersonSale;
use Pushery\Billing\ValueObjects\Money;

$collection = app(CardPresentPayments::class)->collect('tmr_123', new InPersonSale(
sold: TaxArchetype::ConsumerGoods,
gross: Money::of(2_490, 'EUR'),
description: 'Coffee mug, blue',
reference: 'till-1-000417',
));

The gross is the price the buyer was shown, tax included. The description is what the receipt prints as the goods or the service it is for, so a sale cannot go up without one. The reference is yours: a second call with the same reference reaches the same payment, so a retried request does not put the sale up twice.

The sale is taxed in the country of the reader's location, before anything is created at the provider. Goods handed over at the counter are supplied there whoever buys them, so a business from another member state does not move the place with its VAT id, and a tourist pays the tax of the country the shop is in. A service keeps its own place when it is paid in person, so pass the buyer as a TaxContext where you know them. A tip names what it was paid on as soldAlongside. Taxes has the rules.

The reader then shows the gross and waits for the card. collect() does not wait with it: it returns the tax decided for the sale and the provider's reference for the payment.

A sale is refused with ReaderUnavailable, and nothing is created at the provider, when the reader is offline, when it is already taking a payment, or when it stands at no location with a country. cancel() takes a sale off a reader before the buyer has paid.

When the card is presented​

The provider confirms the payment through the webhook, and only then is the sale documented. Until the card was presented nothing had been sold. The receipt states the place, the rate and the split decided when the sale went up, which the package kept in billing_in_person_sales. Nothing is decided again, so the receipt cannot state a different tax than the one the reader charged.

GrossDocument
up to and including billing.card_present.small_amount_threshold_minora simplified invoice: the seller, the date, what was sold, and the gross with its rate as one sum. Germany's limit is €250 (§ 33 UStDV)
above ita payment record, which states the same facts without claiming to be an invoice. A full invoice needs the buyer's name and address, and a buyer at the counter gives none unless they ask for one (see below)

The receipt is numbered in your invoice series (billing.invoices.number_prefix), beside the invoices the package raises for orders. Its owner is the sale, under the morph alias billing_in_person_sale: a buyer at the counter has no account the document could be filed under. A declined card leaves the payment open on the reader, so the buyer can present another one. A payment the provider cancels closes the sale without a receipt.

A buyer who asks for a full invoice​

A business that buys at the counter needs its name and address on the document to deduct the tax, and any buyer may ask for more than a simplified invoice. Restate the receipt they already hold as a full invoice:

use Pushery\Billing\Invoicing\InPersonReceiptIssuer;

$invoice = app(InPersonReceiptIssuer::class)->reissueAsFullInvoice($receipt, [
'name' => 'Tischlerei Holz GmbH',
'line1' => 'Werkstrasse 5',
'postcode' => '80331',
'city' => 'Munich',
'country' => 'DE',
'vat_id' => 'DE123456789',
], now()->toImmutable());

The full invoice gets its own number and names the receipt it restates, so the sale is counted once. Its tax is the receipt's, taken from the receipt's frozen columns: a VAT id does not move goods handed over at the counter. The receipt stays as it was, because an issued document is not taken back. A name and a postal address are required, and a document that is already such a restatement, or is not a receipt from the counter, is refused.

Cards only, and why​

The path takes cards and nothing else. The payment is created for card_present alone, and there is no way to settle part of a sale at the counter with a credit balance, a voucher or a gift card.

That is what keeps the package from being a cash register. Under the German Fiscal Code a system that records payments at the point of sale has a cash-register function as soon as it takes cash, and money substitutes count as cash: vouchers accepted instead of money, stored-value cards, credit balances (AEAO to § 146a, No. 1.2). A system with that function must be protected by a certified security module (§ 146a AO, KassenSichV), and the package has none. A card payment is not cash, so a counter that takes only cards needs none.

If your counter redeems vouchers or takes cash, record those sales in a certified cash register of your own and use the reader path only for sales paid entirely by card.

Where this path stops​

Offline. The server sends the payment to the reader, so a reader the provider cannot reach cannot take it. There is no copy on the reader that would take the card and pass the payment on later, and the package refuses the sale rather than queueing it. Its tax is decided when it goes onto the reader, and its receipt is issued when the payment is confirmed, never before.

Austria. Austrian law counts a card payment at the counter as a cash sale (§ 131b (1) no. 3 BAO), so a shop there must record it in a registered cash register (RKSV). The package is not one. It takes the payment and issues the receipt as anywhere else, and you record each of those sales in your own RKSV register as well.

Other drivers, and sales that are not yours. Only the Stripe driver runs readers, and every sale on one is your own. A reader that takes payments for another seller, paid out to that seller, is not supported.