Skip to main content

Location Services

Learn how to work with physical locations where Paysera services are available, including cash-in, cash-out, and payment services.

What are Locations?​

Locations represent physical places where users can:

  • πŸ’° Cash In - Deposit cash into Paysera account
  • πŸ’΅ Cash Out - Withdraw cash from Paysera account
  • πŸ’³ Pay - Make payments with Paysera account
  • πŸ†” Identify - Verify user identity

Each location has:

  • Address & GPS coordinates
  • Working hours
  • Available services
  • Pricing information
  • Category (for payment locations)

Get All Locations​

Retrieve locations with optional filtering by coordinates, date, and status.

Request

GET /rest/v1/locations?locale={locale}&lat={lat}&lng={lng}&distance={distance}&updated_after={timestamp}&status={status}&limit={limit}&offset={offset}
View Query Parameters
ParameterTypeRequiredDescription
localestring⚠️ RecommendedLanguage for descriptions (e.g., en, lt)
latfloat⬜ OptionalLatitude in decimal format
lngfloat⬜ OptionalLongitude in decimal format
distanceinteger⬜ OptionalDistance in meters (default: 500, requires lat & lng)
updated_afterinteger⬜ OptionalUnix timestamp for filtering by last edit date
statusstring⬜ OptionalFilter by status: active, inactive (default: active)
limitinteger⬜ OptionalMax results (default: 20, max: 200)
offsetinteger⬜ OptionalSkip results (default: 0)
No Authentication Required

This endpoint can be called without authentication.

Response

{
"locations": [
{
"id": 48,
"project_id": 123,
"title": "Paysera Office",
"updated_at": 1382969469,
"status": "active",
"address": "MΔ—nulio g. 7, Vilnius, Lithuania",
"lat": 54.668516,
"lng": 25.235055,
"services": {
"pay": {
"available": true,
"categories": [1]
},
"cash_in": {
"available": true,
"types": ["contact", "document"]
}
}
}
],
"_metadata": {
"total": 1,
"offset": 0,
"limit": 20
}
}
View Location Object Fields
FieldTypeDescription
idintegerLocation ID
project_idintegerAssociated project ID
titlestringLocation name
updated_atintegerUnix timestamp of last update
statusstringactive or inactive
descriptionstringLocation description (optional)
addressstringPhysical address (optional)
latfloatLatitude (optional)
lngfloatLongitude (optional)
radiusintegerLocation radius in meters (optional)
pricesarrayService pricing information (optional)
working_hoursobjectOpening hours by weekday (optional)
servicesobjectAvailable services (optional)
imagesobjectMap pin images
remote_ordersobjectSpot ID for remote orders (optional)
Example: Find Nearby Locations
async function findNearbyLocations(lat, lng, distanceMeters = 2000) {
const response = await api.getLocations({
locale: 'en',
lat: lat,
lng: lng,
distance: distanceMeters,
limit: 50
});

console.log(`Found ${response.locations.length} locations within ${distanceMeters}m`);

return response.locations.map(loc => ({
id: loc.id,
name: loc.title,
address: loc.address,
distance: calculateDistance(lat, lng, loc.lat, loc.lng),
services: Object.keys(loc.services || {}).filter(
s => loc.services[s].available
)
}));
}

// Usage - find locations near Vilnius center
const nearby = await findNearbyLocations(54.687157, 25.279652, 2000);
nearby.forEach(loc => {
console.log(`${loc.name} - ${loc.distance}m - ${loc.services.join(', ')}`);
});

Available Services​

Cash In Service

Deposit cash into Paysera account.

Service types:

TypeDescription
contactWith contact info (email/phone) verification
bar_codeUsing multi-use barcode
documentWith ID document verification
one_time_bar_codeUsing single-use barcode

Example:

{
"cash_in": {
"available": true,
"types": ["contact", "document", "bar_code"]
}
}
Cash Out Service

Withdraw cash from Paysera account.

Service types:

TypeDescription
qr_codeUsing QR code
documentWith ID document verification

Example:

{
"cash_out": {
"available": true,
"types": ["qr_code", "document"]
}
}
Pay Service

Make payments at location.

Features:

  • Category-based locations
  • Special offers
  • Loyalty programs

Example:

{
"pay": {
"available": true,
"categories": [1, 2, 5]
}
}
Identification Service

User identity verification.

Example:

{
"identification": {
"available": true
}
}

Working Hours​

Working Hours Structure

Structure:

{
"monday": {
"from": "08:00",
"to": "20:00"
},
"tuesday": {
"from": "08:00",
"to": "20:00"
},
"saturday": {
"from": "10:00",
"to": "15:00"
}
}

Notes:

  • Missing days = location closed
  • Closing time can be earlier than opening time (closes next day)
  • Times in 24-hour format HH:MM

Check if Open Now:

function isLocationOpenNow(location) {
if (!location.working_hours) return null;

const now = new Date();
const dayName = now.toLocaleDateString('en-US', { weekday: 'lowercase' });
const currentTime = now.toTimeString().slice(0, 5);

const hours = location.working_hours[dayName];
if (!hours) return false;

if (hours.from <= currentTime && currentTime <= hours.to) {
return true;
}

// Handle overnight closing
if (hours.to < hours.from) {
return currentTime >= hours.from || currentTime <= hours.to;
}

return false;
}

Location Categories​

Get all available location categories (for pay service).

Request

GET /rest/v1/locations/pay-categories?locale={locale}

Response

[
{
"id": 1,
"title": "Entertainment"
},
{
"id": 2,
"title": "Bowling",
"parent_id": 1,
"images": {
"active_uri": "https://wallet.paysera.com/assets/locations/pay_category/bowling.png",
"inactive_uri": "https://wallet.paysera.com/assets/locations/pay_category/bowling_off.png"
}
}
]
View Category Fields & Example

Category Object

FieldTypeDescription
idintegerCategory ID
titlestringCategory name
parent_idintegerParent category ID (optional)
imagesobjectCategory icons (optional)

Example: Filter Locations by Category

async function getLocationsByCategory(categoryId, lat, lng) {
// Get all nearby locations
const response = await api.getLocations({
lat, lng,
distance: 5000,
limit: 200
});

// Filter by category
return response.locations.filter(loc => {
const payService = loc.services?.pay;
return payService?.available &&
payService?.categories?.includes(categoryId);
});
}

// Find all restaurants nearby
const restaurants = await getLocationsByCategory(4, 54.68, 25.28);

Advanced Topics​

Pricing Information

Regular Price:

{
"title": "Cash-in fee",
"type": "price",
"price": {
"amount": 100,
"currency": "EUR",
"amount_decimal": "1.00"
}
}

Special Offer:

{
"title": "Cash-out is FREE!",
"type": "offer"
}

Display Pricing:

function formatPrices(location) {
if (!location.prices || location.prices.length === 0) {
return 'No pricing info available';
}

return location.prices.map(p => {
if (p.type === 'offer') {
return `✨ ${p.title}`;
} else if (p.type === 'price') {
return `${p.title}: ${p.price.amount_decimal} ${p.price.currency}`;
}
}).join('\n');
}
Using location_id Parameter

Pass location_id when initiating transactions at specific locations.

When to Use:

  • Payment at physical location
  • Cash-in/cash-out operations
  • Need location context in transaction

Benefits:

  • Influences allowance usage
  • Provides location info to user
  • Better tracking and reporting

How to Pass:

MAC Authentication:

Authorization: MAC id="...", ..., ext="location_id=48&project_id=123"

SSL Certificate:

Wallet-Api-Location-Id: 48
Wallet-Api-Project-Id: 123

Example:

async function createLocationPayment(locationId, paymentData) {
return await api.createTransaction({
payments: paymentData,
headers: {
'Wallet-Api-Location-Id': locationId
}
});
}
Use Case 1: Location Finder App
class LocationFinder {
constructor(api) {
this.api = api;
this.categories = [];
this.locations = [];
}

async initialize() {
this.categories = await this.api.getLocationCategories('en');
}

async searchNearby(lat, lng, service = null, categoryId = null) {
const params = {
locale: 'en',
lat, lng,
distance: 5000,
limit: 100
};

const response = await this.api.getLocations(params);
let results = response.locations;

if (service) {
results = results.filter(loc =>
loc.services?.[service]?.available
);
}

if (categoryId && service === 'pay') {
results = results.filter(loc =>
loc.services?.pay?.categories?.includes(categoryId)
);
}

results.forEach(loc => {
loc.distance = this.calculateDistance(lat, lng, loc.lat, loc.lng);
});

results.sort((a, b) => a.distance - b.distance);

return results;
}

calculateDistance(lat1, lng1, lat2, lng2) {
// Haversine formula
const R = 6371e3;
const Ο†1 = lat1 * Math.PI / 180;
const Ο†2 = lat2 * Math.PI / 180;
const Δφ = (lat2 - lat1) * Math.PI / 180;
const Δλ = (lng2 - lng1) * Math.PI / 180;

const a = Math.sin(Δφ/2) * Math.sin(Δφ/2) +
Math.cos(Ο†1) * Math.cos(Ο†2) *
Math.sin(Δλ/2) * Math.sin(Δλ/2);
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));

return Math.round(R * c);
}
}
Use Case 2: Synchronize Locations
class LocationSync {
constructor(api, storage) {
this.api = api;
this.storage = storage;
}

async syncLocations() {
const lastSync = await this.storage.getLastSyncTime() || 0;

const response = await this.api.getLocations({
updated_after: lastSync,
status: 'active,inactive',
limit: 200
});

console.log(`Syncing ${response.locations.length} updated locations`);

for (const location of response.locations) {
if (location.status === 'active') {
await this.storage.upsertLocation(location);
} else {
await this.storage.deleteLocation(location.id);
}
}

await this.storage.setLastSyncTime(Date.now() / 1000);

return response.locations.length;
}

async syncCategories() {
const categories = await this.api.getLocationCategories('en');
await this.storage.saveCategories(categories);
return categories.length;
}
}
Best Practices

1. Cache Location Data

// Locations don't change frequently
const CACHE_TTL = 24 * 60 * 60 * 1000; // 24 hours

class LocationCache {
async getLocations(params) {
const cacheKey = JSON.stringify(params);
const cached = await storage.get(cacheKey);

if (cached && Date.now() - cached.timestamp < CACHE_TTL) {
return cached.data;
}

const data = await api.getLocations(params);
await storage.set(cacheKey, {
data,
timestamp: Date.now()
});

return data;
}
}

2. Handle GPS Coordinates Properly

// Validate coordinates
function isValidCoordinate(lat, lng) {
return lat >= -90 && lat <= 90 && lng >= -180 && lng <= 180;
}

// Get user location with error handling
async function getUserLocation() {
try {
const position = await new Promise((resolve, reject) => {
navigator.geolocation.getCurrentPosition(resolve, reject);
});

return {
lat: position.coords.latitude,
lng: position.coords.longitude
};
} catch (error) {
console.error('Location access denied');
return { lat: 54.687157, lng: 25.279652 }; // Vilnius fallback
}
}

3. Display Working Hours User-Friendly

function formatWorkingHours(hours) {
if (!hours) return 'Hours not available';

const days = ['monday', 'tuesday', 'wednesday', 'thursday',
'friday', 'saturday', 'sunday'];
const dayNames = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'];

return days.map((day, i) => {
const h = hours[day];
if (!h) return `${dayNames[i]}: Closed`;
return `${dayNames[i]}: ${h.from} - ${h.to}`;
}).join('\n');
}

4. Use Incremental Sync

// Only fetch updated locations
async function incrementalSync() {
const lastSync = await storage.getLastSync() || 0;

const updated = await api.getLocations({
updated_after: lastSync,
status: 'active,inactive',
limit: 200
});

await storage.saveLocations(updated.locations);
await storage.setLastSync(Math.floor(Date.now() / 1000));
}
Common Issues

Issue: Too Many Results

Cause: No distance filtering

Solution:

// Always use distance filter
const locations = await api.getLocations({
lat, lng,
distance: 5000, // 5km radius
limit: 50
});

Issue: Stale Location Data

Cause: Not syncing updates

Solution:

// Regular sync with updated_after
async function regularSync() {
const lastSync = await storage.getLastSyncTime();
const updated = await api.getLocations({
updated_after: lastSync
});
// Process updates...
}

Issue: Wrong Distance Calculation

Cause: Using simple lat/lng diff

Solution:

// Use proper Haversine formula
function haversineDistance(lat1, lng1, lat2, lng2) {
const R = 6371e3;
const Ο†1 = lat1 * Math.PI / 180;
const Ο†2 = lat2 * Math.PI / 180;
const Δφ = (lat2 - lat1) * Math.PI / 180;
const Δλ = (lng2 - lng1) * Math.PI / 180;

const a = Math.sin(Δφ/2) * Math.sin(Δφ/2) +
Math.cos(Ο†1) * Math.cos(Ο†2) *
Math.sin(Δλ/2) * Math.sin(Δλ/2);
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));

return R * c;
}

Next Steps​