Skip to content

Commit

Permalink
Merge pull request #1265 from kaloudis/pos-changes
Browse files Browse the repository at this point in the history
Square POS changes
  • Loading branch information
kaloudis authored Jan 26, 2023
2 parents a634d3c + 35b9f3b commit 4bf79f1
Show file tree
Hide file tree
Showing 8 changed files with 823 additions and 295 deletions.
18 changes: 15 additions & 3 deletions locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,10 @@
"general.pos": "Point of Sale",
"general.admin": "Admin",
"general.pay": "Pay",
"general.open": "Open",
"general.conversionRate": "Conversion rate",
"general.bitcoin": "Bitcoin",
"general.fiat": "Fiat",
"general.fiatFetchError": "Error fetching exchange rates",
"components.CollapsedQr.show": "Show QR",
"components.CollapsedQr.hide": "Hide QR",
Expand Down Expand Up @@ -469,7 +473,9 @@
"views.Settings.POS.enableSquare": "Enable Square POS integration",
"views.Settings.POS.squareAccessToken": "Square Access token",
"views.Settings.POS.squareLocationId": "Square Location ID",
"views.Settings.POS.merchantName": "Merchant name (Optional, used for invoice memos)",
"views.Settings.POS.confPref": "Confirmation preference",
"views.Settings.POS.disableTips": "Disable tips",
"views.Settings.POS.devMode": "Developer mode",
"views.Transaction.title": "Transaction",
"views.Transaction.totalFees": "Total Fees",
Expand Down Expand Up @@ -527,14 +533,20 @@
"error.connectionRefused": "Host unreachable. Try restarting your node or its Tor process.",
"error.hostUnreachable": "Host unreachable. Try restarting your node or its Tor process.",
"error.torBootstrap": "Error starting up Tor on your phone. Try restarting Zeus. If the problem persists consider reinstalling the app.",
"pos.views.Wallet.PosPane.noOrders": "No orders open at the moment",
"pos.views.Wallet.PosPane.noOrders": "No orders open at the moment. To send to ZEUS, mark order as 'Other Payment Type' with a note that includes 'Zeus', 'BTC', or 'Bitcoin'",
"pos.views.Wallet.PosPane.noOrdersPaid": "No orders have been paid yet",
"pos.views.Wallet.PosPane.fetchingRates": "Fetching exchange rates",
"pos.views.Order.tax": "Tax",
"pos.views.Order.totalBeforeTip": "Total before tip",
"pos.views.Order.subtotalBitcoin": "Subtotal (Bitcoin)",
"pos.views.Order.subtotalFiat": "Subtotal (fiat)",
"pos.views.Order.addTip": "Add Tip",
"pos.views.Order.tip": "Tip",
"pos.views.Order.totalFiat": "Total (Fiat)",
"pos.views.Order.tipFiat": "Tip (fiat)",
"pos.views.Order.tipBitcoin": "Tip (Bitcoin)",
"pos.views.Order.totalFiat": "Total (fiat)",
"pos.views.Order.totalBitcoin": "Total (Bitcoin)",
"pos.views.Order.paymentType": "Payment type",
"pos.views.Settings.PointOfSale.authWarning": "Warning: no password or PIN set",
"pos.views.Settings.PointOfSale.backendWarning": "Warning: currently only LND nodes are able to mark orders as paid",
"pos.views.Settings.PointOfSale.currencyError": "Error: currency must be set first"
}
8 changes: 5 additions & 3 deletions models/Order.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
import { computed } from 'mobx';

import moment from 'moment';

import BaseModel from './BaseModel';
import { localeString } from './../utils/LocaleUtils';
import { orderPaymentInfo } from './../stores/PosStore';

interface LineItem {
name: string;
quantity: number;
}

export default class Order extends BaseModel {
id: string;
updated_at: string;
created_at: string;
tax_money: {
total_tax_money: {
amount: number;
currency: string;
};
Expand All @@ -22,6 +23,7 @@ export default class Order extends BaseModel {
currency: string;
};
line_items: Array<LineItem>;
payment?: orderPaymentInfo;

@computed public get model(): string {
return localeString('general.order');
Expand Down Expand Up @@ -61,7 +63,7 @@ export default class Order extends BaseModel {

@computed public get getTaxMoney(): string {
return Number(
((this.tax_money && this.tax_money.amount) || 0) / 100
((this.total_tax_money && this.total_tax_money.amount) || 0) / 100
).toFixed(2);
}

Expand Down
169 changes: 88 additions & 81 deletions stores/PosStore.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,25 @@
import { action, observable } from 'mobx';
import EncryptedStorage from 'react-native-encrypted-storage';
import ReactNativeBlobUtil from 'react-native-blob-util';

import SettingsStore from './SettingsStore';
import Order from '../models/Order';

interface makePaymentRequest {
export interface orderPaymentInfo {
orderId: string;
orderAmount: number;
orderTip: number;
orderTotal: string;
orderTip: string;
exchangeRate: string;
rate: number;
type: string; // ln OR onchain
tx: string; // txid OR payment request
}

const genHex = (size: number) =>
[...Array(size)]
.map(() => Math.floor(Math.random() * 16).toString(16))
.join('');
const genIdemptotencyKey = () =>
`${genHex(8)}-${genHex(4)}-${genHex(4)}-${genHex(4)}-${genHex(12)}}`;

export default class PosStore {
@observable public orders: Array<Order> = [];
@observable public filteredOrders: Array<Order> = [];
@observable public openOrders: Array<Order> = [];
@observable public paidOrders: Array<Order> = [];
@observable public filteredOpenOrders: Array<Order> = [];
@observable public filteredPaidOrders: Array<Order> = [];
@observable public loading = false;
@observable public error = false;

Expand All @@ -31,75 +31,47 @@ export default class PosStore {

@action
public updateSearch = (value: string) => {
this.filteredOrders = this.orders.filter(
this.filteredOpenOrders = this.openOrders.filter(
(item: any) =>
item.getItemsList.includes(value) ||
item.getItemsList.toLowerCase().includes(value)
);
this.filteredPaidOrders = this.paidOrders.filter(
(item: any) =>
item.getItemsList.includes(value) ||
item.getItemsList.toLowerCase().includes(value)
);
};

@action
public makePayment = ({
public recordPayment = ({
orderId,
orderAmount,
orderTip
}: makePaymentRequest) => {
const { squareAccessToken, squareLocationId, squareDevMode } =
this.settingsStore.settings.pos;
const fiat: string = this.settingsStore.settings.fiat || 'USD';
this.loading = true;
this.error = false;
const apiHost = squareDevMode
? 'https://connect.squareupsandbox.com'
: 'https://connect.squareup.com';
ReactNativeBlobUtil.fetch(
'POST',
`${apiHost}/v2/payments`,
{
Authorization: `Bearer ${squareAccessToken}`,
'Content-Type': 'application/json'
},
orderTotal,
orderTip,
exchangeRate,
rate,
type,
tx
}: orderPaymentInfo) =>
EncryptedStorage.setItem(
`pos-${orderId}`,
JSON.stringify({
location_ids: [squareLocationId],
amount_money: {
amount: orderAmount,
currency: fiat
},
idempotency_key: genIdemptotencyKey(),
source_id: 'EXTERNAL',
external_details: {
source: 'ZEUS',
type: 'CRYPTO'
},
order_id: orderId,
tip_money: {
currency: fiat,
amount: orderTip
}
orderId,
orderTotal,
orderTip,
exchangeRate,
rate,
type,
tx
})
)
.then((response: any) => {
const status = response.info().status;
if (status == 200) {
this.loading = false;
} else {
this.loading = false;
this.error = true;
}
})
.catch((err) => {
console.error('POS make payment err', err);
this.loading = false;
});
};
);

@action
public getOrders = (states = ['OPEN']) => {
public getOrders = async () => {
const { squareAccessToken, squareLocationId, squareDevMode } =
this.settingsStore.settings.pos;
this.loading = true;
this.error = false;
this.orders = [];
const apiHost = squareDevMode
? 'https://connect.squareupsandbox.com'
: 'https://connect.squareup.com';
Expand All @@ -111,40 +83,75 @@ export default class PosStore {
'Content-Type': 'application/json'
},
JSON.stringify({
location_ids: [squareLocationId],
query: {
filter: {
state_filter: {
states
}
}
}
location_ids: [squareLocationId]
})
)
.then((response: any) => {
.then(async (response: any) => {
const status = response.info().status;
if (status == 200) {
this.loading = false;
const orders = response
let orders = response
.json()
.orders.map((order: any) => new Order(order));
this.orders = orders;
this.filteredOrders = orders;
.orders.map((order: any) => new Order(order))
.filter((order: any) => {
return (
order.tenders &&
order.tenders[0] &&
order.tenders[0].note &&
(order.tenders[0].note
.toLowerCase()
.includes('zeus') ||
order.tenders[0].note
.toLowerCase()
.includes('zues') ||
order.tenders[0].note
.toLowerCase()
.includes('bitcoin') ||
order.tenders[0].note
.toLowerCase()
.includes('btc'))
);
});

const enrichedOrders = await Promise.all(
orders.map(async (order: any) => {
const payment = await EncryptedStorage.getItem(
`pos-${order.id}`
);
if (payment) order.payment = JSON.parse(payment);
return order;
})
);

const openOrders = enrichedOrders.filter((order: any) => {
return !order.payment;
});
const paidOrders = enrichedOrders.filter((order: any) => {
return order.payment;
});

this.openOrders = openOrders;
this.filteredOpenOrders = openOrders;
this.paidOrders = paidOrders;
this.filteredPaidOrders = paidOrders;
} else {
this.orders = [];
this.openOrders = [];
this.paidOrders = [];
this.loading = false;
this.error = true;
}
})
.catch((err) => {
console.error('POS get orders err', err);
this.orders = [];
this.openOrders = [];
this.paidOrders = [];
this.loading = false;
});
};

resetOrders = () => {
this.orders = [];
this.openOrders = [];
this.paidOrders = [];
this.loading = false;
};
}
4 changes: 4 additions & 0 deletions stores/SettingsStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,9 @@ interface PosSettings {
squareEnabled?: boolean;
squareAccessToken?: string;
squareLocationId?: string;
merchantName?: string;
confirmationPreference?: string;
disableTips?: boolean;
squareDevMode?: boolean;
}

Expand Down Expand Up @@ -211,7 +213,9 @@ export default class SettingsStore {
squareEnabled: false,
squareAccessToken: '',
squareLocationId: '',
merchantName: '',
confirmationPreference: 'lnOnly',
disableTips: false,
squareDevMode: false
},
payments: {
Expand Down
Loading

0 comments on commit 4bf79f1

Please sign in to comment.