import React , { FormEvent , FunctionComponent , useContext , useEffect , useRef , useState , } from 'react' ; import { HostedFieldFieldOptions } from 'braintree-web/modules/hosted-fields' ; import PaymentMethodContext from '#context/PaymentMethodContext' ; import isEmpty from 'lodash/isEmpty' ; import OrderContext from '#context/OrderContext' ; import Parent from './utils/Parent' ; import { PaymentSourceProps } from './PaymentSource' ; import { setCustomerOrderParam } from '#utils/localStorage' ; import promisify from '#utils/promisify' ; type BraintreeHostedFields < Type > = { [ Property in keyof Type ] : { label ? : string ; } & Type [ Property ] ; } ; export type BraintreeConfig = { containerClassName ? : string ; cardContainerClassName ? : string ; expDateContainerClassName ? : string ; fieldsContainerClassName ? : string ; cvvContainerClassName ? : string ; cardDetailsContainerClassName ? : string ; fieldLabelClassName ? : string ; inputWrapperClassName ? : string ; fields ? : BraintreeHostedFields < HostedFieldFieldOptions > ; styles ? : { [ key ] : Record < string , string > ; } ; } ; type BraintreePaymentProps = { authorization : string ; config ? : BraintreeConfig ; templateCustomerSaveToWallet ? : PaymentSourceProps [ 'templateCustomerSaveToWallet' ] ; locale ? : string ; } ; const defaultConfig = { styles : { input : { 'font-size' : '16px' , color : '#3A3A3A' , } , '.number' : { 'font-family' : 'monospace' , } , ':focus' : { color : 'blue' , } , '.valid' : { color : 'green' , } , '.invalid' : { color : 'red' , } , '@media screen and (max-width: 700px)' : { input : { 'font-size' : '14px' , } , } , } , fields : { number : { label : 'Card Number' , selector : '#card-number' , placeholder : '4111 1111 1111 1111' , } , cvv : { label : 'CVV' , selector : '#cvv' , placeholder : '123' , } , expirationDate : { label : 'Expiration Date' , selector : '#expiration-date' , placeholder : '10/2022' , } , } , submitLabel : 'Set payment method' , } ; const BraintreePayment = ( { authorization , config , templateCustomerSaveToWallet , } ) => { const { fields , styles , containerClassName , cardContainerClassName , fieldsContainerClassName , expDateContainerClassName , fieldLabelClassName , cvvContainerClassName , inputWrapperClassName , cardDetailsContainerClassName , } = { ... defaultConfig , ... config } ; const [ loadBraintree , setLoadBraintree ] = useState ( false ) ; const { setPaymentSource , paymentSource , setPaymentMethodErrors , currentPaymentMethodType , setPaymentRef , } = useContext ( PaymentMethodContext ) ; const { order } = useContext ( OrderContext ) ; const ref = useRef < null | HTMLFormElement > ( null ) ; const handleSubmitForm = async ( event ? , hostedFieldsInstance ? , threeDSInstance ? ) => { const savePaymentSourceToCustomerWallet = event ?. elements ?. [ 'save_payment_source_to_customer_wallet' ] ?. checked ; if ( savePaymentSourceToCustomerWallet ) setCustomerOrderParam ( '_save_payment_source_to_customer_wallet' , savePaymentSourceToCustomerWallet ) ; if ( hostedFieldsInstance ) { try { const payload = await promisify ( hostedFieldsInstance ) . then ( ( payload ) => payload ) ; const billingAddress = order ?. billing_address ; const verifyCardOptions = { nonce : payload . nonce , bin : payload . details . bin , amount : order ?. total_amount_with_taxes_float , email : order ?. customer_email , billingAddress : { givenName : billingAddress ?. first_name , surname : billingAddress ?. last_name , phoneNumber : billingAddress ?. phone , streetAddress : billingAddress ?. line_1 , countryCodeAlpha2 : billingAddress ?. country_code , postalCode : billingAddress ?. zip_code , region : billingAddress ?. state_code , locality : billingAddress ?. city , } , onLookupComplete : ( _data , next ) => { next ( ) ; } , } ; const response = await threeDSInstance . verifyCard ( verifyCardOptions ) ; if ( response . rawCardinalSDKVerificationData . Validated && paymentSource ) { paymentSource && ( await setPaymentSource ( { paymentSourceId : paymentSource . id , paymentResource : 'braintree_payments' , attributes : { payment_method_nonce : response . nonce , options : { id : response . nonce , card : { last4 : response . details . lastFour , exp_year : response . details . expirationYear , exp_month : response . details . expirationMonth , brand : response . details . cardType . toLowerCase ( ) , } , } , } , } ) ) ; return true ; } return false ; } catch ( error ) { console . error ( error ) ; setPaymentMethodErrors ( [ { code : 'PAYMENT_INTENT_AUTHENTICATION_FAILURE' , resource : 'payment_methods' , field : currentPaymentMethodType , message : error . message as string , } , ] ) ; return false ; } } return false ; } ; useEffect ( ( ) => { if ( ! ref && authorization ) setCustomerOrderParam ( '_save_payment_source_to_customer_wallet' , 'false' ) ; if ( authorization && ! loadBraintree && ! isEmpty ( window ) ) { const braintreeClient = require ( 'braintree-web/client' ) ; const hostedFields = require ( 'braintree-web/hosted-fields' ) ; const threeDSecure = require ( 'braintree-web/three-d-secure' ) ; braintreeClient . create ( { authorization } , ( clientErr , clientInstance ) => { if ( clientErr ) { console . error ( clientErr ) ; return ; } hostedFields . create ( { client : clientInstance , fields : fields as HostedFieldFieldOptions , styles : styles , } , ( hostedFieldsErr , hostedFieldsInstance ) => { if ( hostedFieldsErr ) { console . error ( hostedFieldsErr ) ; return ; } setLoadBraintree ( true ) ; threeDSecure . create ( { authorization , version : 2 , } , ( threeDSecureErr , threeDSecureInstance ) => { if ( threeDSecureErr ) { console . error ( '3DSecure error' , threeDSecureErr ) ; setPaymentMethodErrors ( [ { code : 'PAYMENT_INTENT_AUTHENTICATION_FAILURE' , resource : 'payment_methods' , field : currentPaymentMethodType , message : threeDSecureErr . message as string , } , ] ) ; } if ( ref . current ) { ref . current . onsubmit = ( ) => handleSubmitForm ( ref . current as any , hostedFieldsInstance , threeDSecureInstance ) ; setPaymentRef ( { ref } ) ; } } ) ; } ) ; } ) ; } return ( ) => { setPaymentRef ( { ref : { current : null } } ) ; setLoadBraintree ( false ) ; } ; } , [ authorization , ref ] ) ; return ! authorization && ! loadBraintree ? null : ( < div className = { containerClassName } > < form ref = { ref } id = "braintree-form" onSubmit = { handleSubmitForm } className = { containerClassName } > < div className = { fieldsContainerClassName } > < div className = { cardContainerClassName } > < label className = { fieldLabelClassName } htmlFor = "card-number" > { fields ?. number ?. label } < /label> < div className = { inputWrapperClassName } id = "card-number" > < /div> < /div> < div className = { cardDetailsContainerClassName } > < div className = { expDateContainerClassName } > < label className = { fieldLabelClassName } htmlFor = "expiration-date" > { fields ?. expirationDate ?. label } < /label> < div className = { inputWrapperClassName } id = "expiration-date" > < /div> < /div> < div className = { cvvContainerClassName } > < label className = { fieldLabelClassName } htmlFor = "cvv" > { fields ?. cvv ?. label } < /label> < div className = { inputWrapperClassName } id = "cvv" > < /div> < /div> < /div> < /div> < div className = { fieldsContainerClassName } > { templateCustomerSaveToWallet && ( < Parent { ... { name : 'save_payment_source_to_customer_wallet' } } > { templateCustomerSaveToWallet } < /Parent>)} < /div> < /form> < /div>); } ; export default BraintreePayment ;