-
Notifications
You must be signed in to change notification settings - Fork 3
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Pin support (LUD-21) #33
Changes from all commits
dc829ff
c163052
5c2baff
d28605b
d634623
a988fc1
fb42dc2
a83a0df
6302cf5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
import { useRef, useState, useEffect } from 'react'; | ||
import ReactNativePinView from "react-native-pin-view"; | ||
import BottomDrawer, { | ||
BottomDrawerMethods, | ||
} from 'react-native-animated-bottom-drawer'; | ||
import { Icon } from "@components"; | ||
import { faDeleteLeft } from "@fortawesome/free-solid-svg-icons"; | ||
import * as S from "./styled"; | ||
|
||
type PinViewFunctions = { | ||
clearAll: () => void; | ||
}; | ||
|
||
type PinPadProps = { | ||
onPinEntered: (pin: string) => void; | ||
} | ||
|
||
const LeftButton = () => <Icon icon={faDeleteLeft} size={36} color={"#FFF"} />; | ||
|
||
export const PinPad = (props: PinPadProps) => { | ||
const pinView = useRef<PinViewFunctions>(null); | ||
const bottomDrawerRef = useRef<BottomDrawerMethods>(null); | ||
const [showRemoveButton, setShowRemoveButton] = useState(false); | ||
const [enteredPin, setEnteredPin] = useState(""); | ||
const [buttonPressed, setButtonPressed] = useState(""); | ||
|
||
useEffect(() => { | ||
if (enteredPin.length > 0) { | ||
setShowRemoveButton(true) | ||
} else { | ||
setShowRemoveButton(false) | ||
} | ||
if (enteredPin.length === 4) { | ||
props.onPinEntered(enteredPin) | ||
} | ||
}, [enteredPin]); | ||
|
||
useEffect(() => { | ||
if (buttonPressed === "custom_left" && pinView.current) { | ||
pinView.current.clearAll() | ||
} | ||
}, [buttonPressed]); | ||
|
||
return ( | ||
<> | ||
<BottomDrawer | ||
ref={bottomDrawerRef} | ||
openOnMount gestureMode={"none"} | ||
closeOnBackdropPress={false} | ||
initialHeight={535} | ||
backdropOpacity={0} | ||
customStyles={S.BottomDrawerStyles} | ||
> | ||
<S.PinPadContainer> | ||
<S.PinPadTitle> | ||
Boltcard PIN | ||
</S.PinPadTitle> | ||
<ReactNativePinView | ||
inputSize={32} | ||
// @ts-ignore | ||
ref={pinView} | ||
pinLength={4} | ||
buttonSize={60} | ||
onValueChange={value => setEnteredPin(value)} | ||
buttonAreaStyle={{ marginTop: 24 }} | ||
inputAreaStyle={{ marginBottom: 24 }} | ||
inputViewEmptyStyle={S.PinViewStyles.whiteBorderTransparent} | ||
inputViewFilledStyle={{ backgroundColor: "#FFF" }} | ||
buttonViewStyle={S.PinViewStyles.whiteBorder} | ||
buttonTextStyle={{ color: "#FFF" }} | ||
onButtonPress={key => setButtonPressed(key)} | ||
// @ts-ignore | ||
customLeftButton={showRemoveButton ? <LeftButton /> : undefined} | ||
/> | ||
</S.PinPadContainer> | ||
</BottomDrawer > | ||
</> | ||
); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export { PinPad } from "./PinPad"; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
import styled from "styled-components"; | ||
import { View, Text } from "@components"; | ||
import { StyleSheet } from "react-native"; | ||
|
||
export const PinPadContainer = styled(View)` | ||
backgroundColor: transparent; | ||
alignItems: center; | ||
flex: 1; | ||
`; | ||
|
||
export const PinPadTitle = styled(Text)` | ||
paddingTop: 48px; | ||
paddingBottom: 24px; | ||
color: rgba(255,255,255,1); | ||
fontSize: 48px; | ||
`; | ||
|
||
export const BottomDrawerStyles = StyleSheet.create({ | ||
container: { | ||
backgroundColor: "rgba(0,0,0,0.5)", | ||
}, | ||
handleContainer: { | ||
backgroundColor: "transparent", | ||
} | ||
}); | ||
|
||
export const PinViewStyles = StyleSheet.create({ | ||
whiteBorder: { | ||
borderWidth: 1, | ||
borderColor: "#FFF", | ||
}, | ||
whiteBorderTransparent: { | ||
borderWidth: 1, | ||
borderColor: "#FFF", | ||
backgroundColor: "transparent" | ||
}, | ||
}); |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -17,6 +17,31 @@ export const useNfc = () => { | |||||
const [isNfcNeedsTap, setIsNfcNeedsTap] = useState(false); | ||||||
const [isNfcNeedsPermission, setIsNfcNeedsPermission] = useState(false); | ||||||
const [isNfcActionSuccess, setIsNfcActionSuccess] = useState(false); | ||||||
const [isPinRequired, setIsPinRequired] = useState(false); | ||||||
const [isPinConfirmed, setIsPinConfirmed] = useState(false); | ||||||
|
||||||
const [ pinResolver, setPinResolver ] = useState<{ | ||||||
resolve: (v: string) => void; | ||||||
}>(); | ||||||
|
||||||
const setPin = (pin: string) => { | ||||||
if(pinResolver?.resolve) { | ||||||
pinResolver.resolve(pin); | ||||||
} | ||||||
} | ||||||
|
||||||
const getPin = useCallback(async () => { | ||||||
const pinInputPromise = () => { | ||||||
let _pinResolver; | ||||||
return [ new Promise<string>(( resolve ) => { | ||||||
_pinResolver = resolve | ||||||
}), _pinResolver] | ||||||
} | ||||||
|
||||||
const [ promise, resolve ] = pinInputPromise() | ||||||
setPinResolver({ resolve } as unknown as { resolve: (v: string) => void }) | ||||||
return promise as Promise<string>; | ||||||
}, []); | ||||||
|
||||||
const setupNfc = useCallback(async () => { | ||||||
if (await getIsNfcSupported()) { | ||||||
|
@@ -49,7 +74,8 @@ export const useNfc = () => { | |||||
}, []); | ||||||
|
||||||
const readingNfcLoop = useCallback( | ||||||
async (pr: string) => { | ||||||
async (pr: string, amount?: number | null) => { | ||||||
|
||||||
setIsNfcActionSuccess(false); | ||||||
await NFC.stopRead(); | ||||||
|
||||||
|
@@ -73,7 +99,7 @@ export const useNfc = () => { | |||||
}); | ||||||
|
||||||
if (!isIos) { | ||||||
readingNfcLoop(pr); | ||||||
readingNfcLoop(pr, amount); | ||||||
} else { | ||||||
setIsNfcAvailable(false); | ||||||
} | ||||||
|
@@ -114,19 +140,41 @@ export const useNfc = () => { | |||||
tag: "withdrawRequest"; | ||||||
callback: string; | ||||||
k1: string; | ||||||
pinLimit?: number; | ||||||
}>(cardData); | ||||||
|
||||||
const { data: callbackResponseData } = await axios.get<{ | ||||||
reason: { detail: string }; | ||||||
status: "OK" | "ERROR"; | ||||||
}>(cardDataResponse.callback, { | ||||||
params: { | ||||||
k1: cardDataResponse.k1, | ||||||
pr | ||||||
let pin = ""; | ||||||
if (cardDataResponse.pinLimit !== undefined) { | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Simplify it with:
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I was thinking that the pinLimit could be set to 0. But then one could argue that means it is "disabled" since the number in the pinLimit is the min amounted needed before asking the pin and the smallest possible number for that would be 1. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Interesting. The doc says |
||||||
|
||||||
if (!amount) { | ||||||
error = { reason: "No amount set. Can't make withdrawRequest"} | ||||||
} else { | ||||||
//if the card has pin enabled | ||||||
//check the amount didn't exceed the limit | ||||||
const limitSat = cardDataResponse.pinLimit; | ||||||
if (limitSat <= amount) { | ||||||
setIsPinRequired(true); | ||||||
pin = await getPin(); | ||||||
setIsPinConfirmed(true); | ||||||
} | ||||||
} | ||||||
}); | ||||||
} else { | ||||||
setIsPinRequired(false); | ||||||
} | ||||||
|
||||||
debitCardData = callbackResponseData; | ||||||
if (!error) { | ||||||
const { data: callbackResponseData } = await axios.get<{ | ||||||
reason: { detail: string }; | ||||||
status: "OK" | "ERROR"; | ||||||
}>(cardDataResponse.callback, { | ||||||
params: { | ||||||
k1: cardDataResponse.k1, | ||||||
pr, | ||||||
pin | ||||||
} | ||||||
}) | ||||||
debitCardData = callbackResponseData; | ||||||
} | ||||||
} else { | ||||||
// const { data: cardRequest } = await axios.get<{ payLink?: string }>( | ||||||
// lnHttpsRequest | ||||||
|
@@ -190,9 +238,14 @@ export const useNfc = () => { | |||||
await NFC.stopRead(); | ||||||
|
||||||
setIsNfcLoading(false); | ||||||
setIsPinConfirmed(false); | ||||||
setIsPinRequired(false); | ||||||
if (debitCardData?.status === "OK" && !error) { | ||||||
setIsNfcActionSuccess(true); | ||||||
} else { | ||||||
if (debitCardData?.status === "ERROR" && !error) { | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If the pin is wrong the response is in the debitCardData object not the error object. Is that a wrong implementation on my side? Should the server send another status then 200? I haven't seen a specification for that in the LUD-3. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That's probably a mis-implementation on our side. You can keep the code like this for the moment. |
||||||
error = debitCardData; | ||||||
} | ||||||
toast.show( | ||||||
typeof error?.reason === "string" | ||||||
? error.reason | ||||||
|
@@ -203,7 +256,7 @@ export const useNfc = () => { | |||||
); | ||||||
|
||||||
if (!isIos) { | ||||||
readingNfcLoop(pr); | ||||||
readingNfcLoop(pr, amount); | ||||||
} | ||||||
} | ||||||
|
||||||
|
@@ -213,7 +266,7 @@ export const useNfc = () => { | |||||
} | ||||||
}); | ||||||
}, | ||||||
[toast, t] | ||||||
[toast, t, getPin] | ||||||
); | ||||||
|
||||||
const stopNfc = useCallback(() => { | ||||||
|
@@ -233,6 +286,9 @@ export const useNfc = () => { | |||||
isNfcNeedsTap, | ||||||
isNfcNeedsPermission, | ||||||
isNfcActionSuccess, | ||||||
isPinRequired, | ||||||
isPinConfirmed, | ||||||
setPin, | ||||||
setupNfc, | ||||||
stopNfc, | ||||||
readingNfcLoop | ||||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To avoid a new TS issue here, add a
// @ts-ignore
: