Skip to content

Commit 7032843

Browse files
Merge pull request #262 from SwissBitcoinPay/download-receipt
Download receipt
2 parents b0067ea + 48c1ba8 commit 7032843

9 files changed

Lines changed: 115 additions & 17 deletions

File tree

.env

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
APP_VERSION=2.5.2
1+
APP_VERSION=2.5.3
22

3-
APP_BUILD_NUMBER=428
3+
APP_BUILD_NUMBER=429
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
- Added QR to download the receipt after payment
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
- Ajouté un QR code de téléchargement du reçu après paiement

src/assets/translations/en.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,9 @@
160160
"scanToVerify": "Scan to verify your identity",
161161
"or": "or",
162162
"scanToWithdraw": "Scan to receive bitcoin via",
163-
"printReceipt": "Print receipt"
163+
"printReceipt": "Print receipt",
164+
"receipt": "Receipt",
165+
"downloadReceipt": "Download receipt"
164166
},
165167
"history": {
166168
"title": "Transaction history",

src/assets/translations/fr.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,9 @@
160160
"scanToVerify": "Scanner pour vérifier votre identité",
161161
"or": "ou",
162162
"scanToWithdraw": "Scanner pour recevoir des bitcoins via",
163-
"printReceipt": "Imprimer reçu"
163+
"printReceipt": "Imprimer reçu",
164+
"receipt": "Reçu",
165+
"downloadReceipt": "Télécharger le reçu"
164166
},
165167
"history": {
166168
"title": "Historique des transactions",

src/components/QR/index.tsx

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,15 @@ type QRProps = Omit<QRCodeProps, "ref"> & {
6161
{ icon?: IconProp }
6262
>;
6363

64-
export const QR = ({ style, image, icon, size = 0, ...props }: QRProps) => {
64+
export const QR = ({
65+
style,
66+
image,
67+
icon,
68+
size = 0,
69+
logoBackgroundColor,
70+
logoColor,
71+
...props
72+
}: QRProps) => {
6573
const theme = useTheme();
6674
const { padding, borderRadius } = useMemo(
6775
() => extractPaddingFromStyle(style as React.CSSProperties),
@@ -79,9 +87,13 @@ export const QR = ({ style, image, icon, size = 0, ...props }: QRProps) => {
7987
style={{ transform: [{ scale: image.scale || 1 }] }}
8088
/>
8189
) : icon ? (
82-
<S.QRIconContinaer>
83-
<Icon icon={icon} size={50} color={theme.colors.primary} />
84-
</S.QRIconContinaer>
90+
<S.QRIconContainer style={{ backgroundColor: logoBackgroundColor }}>
91+
<Icon
92+
icon={icon}
93+
size={size / 5}
94+
color={logoColor || theme.colors.primary}
95+
/>
96+
</S.QRIconContainer>
8597
) : null}
8698
</S.QRContainer>
8799
);

src/components/QR/styled.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import styled from "styled-components";
2-
import { Icon, Image, View } from "@components";
2+
import { Image, View } from "@components";
33

44
export const QRContainer = styled(View)`
55
background: ${({ theme }) => theme.colors.white};
@@ -19,10 +19,10 @@ export const QRImage = styled(Image)`
1919
height: 70px;
2020
`;
2121

22-
export const QRIconContinaer = styled(View)`
22+
export const QRIconContainer = styled(View)`
2323
position: absolute;
2424
background-color: ${({ theme }) => theme.colors.white};
25-
padding: 8px;
25+
padding: 2%;
2626
border-radius: 100px;
2727
align-items: center;
2828
justify-content: center;

src/screens/Invoice/Invoice.tsx

Lines changed: 58 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,17 @@ import {
2222
QR,
2323
CountdownCircleTimer,
2424
Pressable,
25-
Modal
25+
Modal,
26+
View
2627
} from "@components";
2728
import {
2829
faArrowLeft,
2930
faArrowUpRightFromSquare,
3031
faBolt,
3132
faCheck,
33+
faCircleDown,
3234
faClock,
35+
faFileDownload,
3336
faGlobe,
3437
faHandPointer,
3538
faIdCard,
@@ -55,6 +58,7 @@ import { FooterLine } from "./components/FooterLine";
5558
import {
5659
DEFAULT_DECIMALS,
5760
apiRootDomain,
61+
apiRootUrl,
5862
appRootUrl,
5963
currencies,
6064
rateUpdateDelay
@@ -162,13 +166,15 @@ const STATUS_ICON_SIZE = 120;
162166
const MAX_QR_SIZE = 320;
163167
const FOOTER_VALUE_ITEMS_SIZE = 18;
164168

169+
const REDIRECT_DELAY = 60 * 1000;
170+
165171
export const Invoice = () => {
166172
const navigate = useNavigate();
167173
const { colors, gridSize } = useTheme();
168174
const toast = useToast();
169175
const versionTag = useVersionTag();
170176
const printInvoiceTicket = usePrintInvoiceTicket();
171-
const { t } = useTranslation(undefined, {
177+
const { t, i18n } = useTranslation(undefined, {
172178
keyPrefix: "screens.invoice"
173179
});
174180
const { t: tRoot } = useTranslation();
@@ -396,8 +402,6 @@ export const Invoice = () => {
396402
delay: springAnimationDelay
397403
});
398404

399-
const REDIRECT_DELAY = 7000;
400-
401405
redirectProgressApi.start({
402406
to: { left: "0%" },
403407
config: { duration: REDIRECT_DELAY }
@@ -539,7 +543,10 @@ export const Invoice = () => {
539543
);
540544
}
541545

542-
setDescription(getInvoiceData.description);
546+
if (getInvoiceData.description) {
547+
// don't remove description if it already stored
548+
setDescription(getInvoiceData.description);
549+
}
543550
setAmount(getInvoiceData.amount * 1000);
544551
setPaidAt(getInvoiceData.paidAt);
545552
setInvoiceCurrency(getInvoiceData.input.unit || "CHF");
@@ -736,6 +743,12 @@ export const Invoice = () => {
736743
printInvoiceTicket
737744
]);
738745

746+
const downloadPdfLink = useMemo(
747+
() =>
748+
`${apiRootUrl}/pdf/${invoiceId}?lng=${i18n.language}&tz=${Intl.DateTimeFormat().resolvedOptions().timeZone}`,
749+
[i18n.language, invoiceId]
750+
);
751+
739752
const getPageContainerProps = useCallback(
740753
(isSuccessScreen = false) => {
741754
return {
@@ -788,6 +801,13 @@ export const Invoice = () => {
788801
[qrCodeSize, gridSize]
789802
);
790803

804+
const redirectDuration = useMemo(() => {
805+
if (!isExternalInvoice) {
806+
return REDIRECT_DELAY / 1000;
807+
}
808+
return 7;
809+
}, [isExternalInvoice]);
810+
791811
return (
792812
<>
793813
<Modal
@@ -811,6 +831,23 @@ export const Invoice = () => {
811831
>
812832
<S.InvoicePageContainer {...getPageContainerProps(true)}>
813833
<S.SectionsContainer gapSize={2}>
834+
{!isWithdraw && (
835+
<S.InvoiceDownloadContainer isLarge={isLarge}>
836+
<QR
837+
value={downloadPdfLink}
838+
size={S.INVOICE_DOWNLOAD_QR}
839+
icon={faCircleDown}
840+
ecl="M"
841+
backgroundColor={colors.white}
842+
logoBackgroundColor={colors.white}
843+
color={colors.success}
844+
logoColor={colors.success}
845+
/>
846+
<Text centered weight={600} color={colors.success} h4>
847+
{t("receipt")}
848+
</Text>
849+
</S.InvoiceDownloadContainer>
850+
)}
814851
<S.Section grow>
815852
<>
816853
<S.TypeText color="transparent">_</S.TypeText>
@@ -1025,7 +1062,7 @@ export const Invoice = () => {
10251062
<CountdownCircleTimer
10261063
isGrowing
10271064
isPlaying
1028-
duration={7}
1065+
duration={redirectDuration}
10291066
strokeWidth={5}
10301067
size={STATUS_ICON_SIZE / 4}
10311068
colors={colors.white}
@@ -1143,6 +1180,21 @@ export const Invoice = () => {
11431180
</ComponentStack>
11441181
</ComponentStack>
11451182
)}
1183+
{status === "settled" && !isWithdraw && (
1184+
<View
1185+
style={{
1186+
flex: 1,
1187+
justifyContent: "flex-end",
1188+
width: "100%"
1189+
}}
1190+
>
1191+
<Button
1192+
icon={faFileDownload}
1193+
title={t("downloadReceipt")}
1194+
onPress={downloadPdfLink}
1195+
/>
1196+
</View>
1197+
)}
11461198
{createdAt && delay && isAlive && !isExternalInvoice && (
11471199
<S.ProgressBar
11481200
progress={progress}

src/screens/Invoice/styled.tsx

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import { platform } from "@config";
1616
import { Circle } from "react-native-progress";
1717
import { animated } from "@react-spring/native";
1818
import { ColorValue } from "react-native";
19+
import { getShadow } from "@utils";
1920

2021
const { isNative, maxContentWidth } = platform;
2122

@@ -63,6 +64,33 @@ export const SectionsContainer = styled(ComponentStack)`
6364
flex-grow: 1;
6465
`;
6566

67+
export const INVOICE_DOWNLOAD_QR = 115;
68+
const INVOICE_DOWNLOAD_PADDING = 12;
69+
70+
export const InvoiceDownloadContainer = styled(ComponentStack).attrs(() => ({
71+
direction: "vertical",
72+
gapSize: 6
73+
}))<{ isLarge: boolean }>`
74+
position: absolute;
75+
top: -22px;
76+
right: 0px;
77+
padding: ${INVOICE_DOWNLOAD_PADDING}px;
78+
width: ${INVOICE_DOWNLOAD_QR + 2 * INVOICE_DOWNLOAD_PADDING}px;
79+
border-radius: ${({ theme }) => theme.borderRadius}px;
80+
align-items: center;
81+
text-align: center;
82+
background-color: ${({ theme }) => theme.colors.white};
83+
${getShadow({ level: 16 })}
84+
${({ theme, isLarge }) =>
85+
!isLarge
86+
? `
87+
right: -${theme.gridSize}px;
88+
border-top-right-radius: 0px;
89+
border-bottom-right-radius: 0px;
90+
`
91+
: ``}
92+
`;
93+
6694
export const Section = styled(ComponentStack)<{ grow?: boolean }>`
6795
align-items: center;
6896
overflow: hidden;

0 commit comments

Comments
 (0)