Receipts & shopper engagement

Print, email, or SMS receipts and use the terminal screen to capture shopper data.

View as MarkdownInstall skills

VINR terminals do more than process payments — they deliver receipts across print, email, and SMS channels and turn the idle screen into a branded surface for capturing shopper data. This page covers how to configure receipt delivery, customise receipt content, and use terminal prompts to collect email addresses and loyalty numbers at the point of sale.

Receipt typesAsk

After a successful transaction the terminal presents a receipt-delivery prompt. The shopper selects their preferred channel, or the merchant can pre-configure a default and skip the prompt entirely.

Prop

Type

The terminal prompt flow works as follows:

Transaction is authorised. The terminal displays a Receipt? screen.

The shopper selects Print, Email, SMS, or No receipt. Channels not available for the device (e.g. Print on a Ciontek CM30) are hidden automatically.

If Email or SMS is chosen and no address or number is on file, the terminal keyboard opens for the shopper to enter their details.

The receipt is delivered and the terminal returns to its idle screen.

Configure receipt settingsAsk

Default receipt behaviour is set per-terminal in Dashboard → Terminals → [select terminal] → Receipt settings. You can also set it programmatically when creating or updating a terminal payment.

import { Vinr } from '@vinr/sdk';

const vinr = new Vinr({ secretKey: process.env.VINR_SECRET_KEY });

const terminalPayment = await vinr.terminal.payments.create({
  terminalId: 'term_01HZ5QXYZ',
  amount: 2500,
  currency: 'USD',
  reference: 'order_8821',
  receipt: {
    channels: ['email', 'paper'],
    requireEmail: false,
  },
});
import vinr
import os

client = vinr.Vinr(secret_key=os.environ["VINR_SECRET_KEY"])

terminal_payment = client.terminal.payments.create(
    terminal_id="term_01HZ5QXYZ",
    amount=2500,
    currency="USD",
    reference="order_8821",
    receipt={
        "channels": ["email", "paper"],
        "require_email": False,
    },
)
curl https://api.vinr.com/v1/terminal/payments \
  -u "$VINR_SECRET_KEY:" \
  -d terminal_id=term_01HZ5QXYZ \
  -d amount=2500 \
  -d currency=USD \
  -d reference=order_8821 \
  -d "receipt[channels][]=email" \
  -d "receipt[channels][]=paper" \
  -d "receipt[require_email]=false"

channels controls which delivery options are shown to the shopper. Set requireEmail: true to make the email collection step mandatory before the transaction can complete — useful when you need every transaction tied to an email address.

Receipt contentAsk

Every VINR receipt includes a fixed set of auto-populated fields sourced from the payment and terminal records.

Auto-populated fields

  • Merchant trading name and registered address
  • Terminal ID and terminal location label
  • Transaction amount, currency, and tip (if collected)
  • Card scheme, masked PAN (last four digits), and entry method
  • Authorisation code and date/time (UTC)
  • VINR payment reference

Custom fields via receiptData

Pass a receiptData map on the payment to append merchant-defined lines. The fields appear as a labelled section at the bottom of the receipt, above the footer.

import { Vinr } from '@vinr/sdk';

const vinr = new Vinr({ secretKey: process.env.VINR_SECRET_KEY });

const terminalPayment = await vinr.terminal.payments.create({
  terminalId: 'term_01HZ5QXYZ',
  amount: 4800,
  currency: 'USD',
  reference: 'order_9041',
  receipt: {
    channels: ['email', 'paper'],
  },
  receiptData: {
    'Order number': 'ORD-9041',
    'Served by': 'Alex',
    'Table': '12',
  },
});

Prop

Type

Terminal displayAsk

The terminal idle screen — shown between transactions — can display your logo and brand colours. A well-branded idle screen reduces shopper hesitation and reinforces trust at the counter.

Configure the idle screen in Dashboard → Terminals → Branding. Changes propagate to all terminals in your account within five minutes.

Open Dashboard → Terminals → Branding.

Upload a logo in PNG format, minimum 300 × 300 px, maximum 1 MB. Transparent backgrounds are supported. The logo is automatically scaled for each device's screen size.

Set a primary colour using the hex picker. This colour is used for the background of the idle screen and for button highlights on the Nexgo N92 and Nexgo N86Pro.

Click Save and deploy. A preview renders immediately in the browser. Live terminals update in the background — no reboot required.

The Ciontek CM30 displays the idle screen on the merchant's paired phone or tablet, not on the device itself. Upload a widescreen (16:9) variant of your logo for best results on phone screens.

Input promptsAsk

Use terminalPayment.prompts to collect structured data from shoppers during the payment flow — before or after the card interaction. Common uses are capturing an email address for a digital receipt or collecting a loyalty card number.

import { Vinr } from '@vinr/sdk';

const vinr = new Vinr({ secretKey: process.env.VINR_SECRET_KEY });

const terminalPayment = await vinr.terminal.payments.create({
  terminalId: 'term_01HZ5QXYZ',
  amount: 3200,
  currency: 'USD',
  reference: 'order_9102',
  prompts: [
    {
      id: 'email',
      label: 'Email for receipt',
      type: 'email',
      required: false,
      timing: 'before',
    },
    {
      id: 'loyalty_number',
      label: 'Loyalty card number',
      type: 'text',
      required: false,
      timing: 'before',
    },
  ],
});

Prompt responses are available on the completed payment object under payment.terminalData.prompts, keyed by the id you supplied.

const payment = await vinr.terminal.payments.retrieve(terminalPayment.id);

const email = payment.terminalData?.prompts?.email;
const loyaltyNumber = payment.terminalData?.prompts?.loyalty_number;

Prop

Type

Data collected via prompts is personal data under GDPR, CCPA, and equivalent regulations. You must have a lawful basis for collection, display a clear purpose statement at the point of capture, and handle the data in accordance with your privacy policy. VINR stores prompt responses for 90 days to support dispute resolution; you are the data controller for any downstream use.

Loyalty integrationAsk

Collect a loyalty card number at the terminal using a numeric prompt, then look up or create the shopper's loyalty account server-side before the transaction completes — or post-payment via webhook.

import { Vinr } from '@vinr/sdk';

const vinr = new Vinr({ secretKey: process.env.VINR_SECRET_KEY });

const terminalPayment = await vinr.terminal.payments.create({
  terminalId: 'term_01HZ5QXYZ',
  amount: 5500,
  currency: 'USD',
  reference: 'order_9215',
  prompts: [
    {
      id: 'loyalty_number',
      label: 'Loyalty card number (optional)',
      type: 'numeric',
      required: false,
      timing: 'before',
    },
  ],
  receipt: {
    channels: ['paper', 'email'],
  },
});

After the payment completes, retrieve the loyalty number from the webhook payload and call the loyalty API to award points. See Loyalty accounts for the full points-award workflow.

To display the earned points balance on the printed or digital receipt, include a receiptData entry populated with the result from the loyalty award call:

const loyaltyResult = await vinr.loyalty.accounts.awardPoints({
  accountId: loyaltyAccountId,
  points: 55,
  reference: terminalPayment.id,
});

const terminalPayment = await vinr.terminal.payments.create({
  terminalId: 'term_01HZ5QXYZ',
  amount: 5500,
  currency: 'USD',
  reference: 'order_9215',
  receiptData: {
    'Points earned': String(loyaltyResult.pointsAwarded),
    'Points balance': String(loyaltyResult.newBalance),
  },
});

Next stepsAsk

Was this page helpful?
Edit on GitHub

Last updated on

On this page