diff --git a/src/Components/SavedMethods.res b/src/Components/SavedMethods.res index 6e65020b9..ab0324f1e 100644 --- a/src/Components/SavedMethods.res +++ b/src/Components/SavedMethods.res @@ -160,7 +160,7 @@ let make = ( | Some("apple_pay") => switch applePayToken { | ApplePayTokenOptional(optToken) => - ApplePayHelpers.handleApplePayButtonClicked(~sessionObj=optToken, ~componentName) + ApplePayHelpers.handleApplePayButtonClicked(~sessionObj=optToken, ~componentName, ~paymentMethodListValue) | _ => // TODO - To be replaced with proper error message intent( diff --git a/src/Payments/ApplePay.res b/src/Payments/ApplePay.res index 840fc9736..afbc951e0 100644 --- a/src/Payments/ApplePay.res +++ b/src/Payments/ApplePay.res @@ -241,7 +241,7 @@ let make = (~sessionObj: option, ~walletOptions, ~paymentType: CardTheme ~isManualRetryEnabled, ) } else { - ApplePayHelpers.handleApplePayButtonClicked(~sessionObj, ~componentName) + ApplePayHelpers.handleApplePayButtonClicked(~sessionObj, ~componentName, ~paymentMethodListValue) } } else { let bodyDict = PaymentBody.applePayRedirectBody(~connectors) diff --git a/src/Payments/PaymentMethodsRecord.res b/src/Payments/PaymentMethodsRecord.res index f72b3b3f0..5803f6aba 100644 --- a/src/Payments/PaymentMethodsRecord.res +++ b/src/Payments/PaymentMethodsRecord.res @@ -804,6 +804,7 @@ type paymentMethodList = { payment_type: payment_type, merchant_name: string, collect_billing_details_from_wallets: bool, + is_tax_calculation_enabled: bool, } let defaultPaymentMethodType = { @@ -826,6 +827,7 @@ let defaultList = { payment_type: NONE, merchant_name: "", collect_billing_details_from_wallets: true, + is_tax_calculation_enabled: false, } let getPaymentExperienceType = str => { @@ -1025,6 +1027,7 @@ let itemToObjMapper = dict => { "collect_billing_details_from_wallets", true, ), + is_tax_calculation_enabled: getBool(dict,"is_tax_calculation_enabled",false) } } diff --git a/src/Types/ApplePayTypes.res b/src/Types/ApplePayTypes.res index d7a4040eb..29b00e88c 100644 --- a/src/Types/ApplePayTypes.res +++ b/src/Types/ApplePayTypes.res @@ -25,6 +25,13 @@ type shippingContact = { type paymentResult = {token: JSON.t, billingContact: JSON.t, shippingContact: JSON.t} type event = {validationURL: string, payment: paymentResult} +type lineItem = { + label: string, + amount: string, + \"type": string, +} +type shippingAddressChangeEvent = {shippingContact: JSON.t} +type updatedOrderDetails = {newTotal: lineItem, newLineItems: array} type innerSession type session = { begin: unit => unit, @@ -34,6 +41,8 @@ type session = { mutable onvalidatemerchant: event => unit, completeMerchantValidation: JSON.t => unit, mutable onpaymentauthorized: event => unit, + mutable onshippingcontactselected: shippingAddressChangeEvent => unit, + completeShippingContactSelection: updatedOrderDetails => unit, completePayment: JSON.t => unit, \"STATUS_SUCCESS": string, \"STATUS_FAILURE": string, diff --git a/src/Utilities/ApplePayHelpers.res b/src/Utilities/ApplePayHelpers.res index d23f0ae2c..b481b55f2 100644 --- a/src/Utilities/ApplePayHelpers.res +++ b/src/Utilities/ApplePayHelpers.res @@ -1,5 +1,6 @@ open ApplePayTypes open Utils +open TaxCalculation let processPayment = ( ~bodyArr, @@ -75,7 +76,11 @@ let startApplePaySession = ( ~applePayEvent: option=None, ~callBackFunc, ~resolvePromise: option unit>=None, + ~clientSecret, + ~publishableKey, + ~isTaxCalculationEnabled=false, ) => { + open Promise let ssn = applePaySession(3, paymentRequest) switch applePaySessionRef.contents->Nullable.toOption { | Some(session) => @@ -100,6 +105,67 @@ let startApplePaySession = ( ssn.completeMerchantValidation(merchantSession) } + ssn.onshippingcontactselected = shippingAddressChangeEvent => { + if isTaxCalculationEnabled { + let newShippingContact = + shippingAddressChangeEvent.shippingContact + ->getDictFromJson + ->shippingContactItemToObjMapper + let newShippingAddress = + [ + ("state", newShippingContact.locality->JSON.Encode.string), + ("country", newShippingContact.countryCode->JSON.Encode.string), + ("zip", newShippingContact.postalCode->JSON.Encode.string), + ]->getJsonFromArrayOfJson + + let paymentMethodType = "apple_pay"->JSON.Encode.string + + calculateTax( + ~shippingAddress=[("address", newShippingAddress)]->getJsonFromArrayOfJson, + ~logger, + ~publishableKey, + ~clientSecret, + ~paymentMethodType, + ) + ->then(response => { + let taxCalculationResponse = response->getDictFromJson->taxResponseToObjMapper + let (netAmount, ordertaxAmount, shippingCost) = ( + taxCalculationResponse.net_amount, + taxCalculationResponse.order_tax_amount, + taxCalculationResponse.shipping_cost, + ) + let newTotal: lineItem = { + label: "Net Amount", + amount: netAmount->Int.toString, + \"type": "final", + } + let newLineItems: array = [ + { + label: "Bag Subtotal", + amount: (netAmount - ordertaxAmount - shippingCost)->Int.toString, + \"type": "final", + }, + { + label: "Order Tax Amount", + amount: ordertaxAmount->Int.toString, + \"type": "final", + }, + { + label: "Shipping Cost", + amount: shippingCost->Int.toString, + \"type": "final", + }, + ] + let updatedOrderDetails: updatedOrderDetails = { + newTotal, + newLineItems, + } + ssn.completeShippingContactSelection(updatedOrderDetails)->resolve + }) + ->ignore + } + } + ssn.onpaymentauthorized = event => { ssn.completePayment({"status": ssn.\"STATUS_SUCCESS"}->Identity.anyTypeToJson) applePaySessionRef := Nullable.null @@ -231,11 +297,19 @@ let useHandleApplePayResponse = ( )) } -let handleApplePayButtonClicked = (~sessionObj, ~componentName) => { +let handleApplePayButtonClicked = ( + ~sessionObj, + ~componentName, + ~paymentMethodListValue: PaymentMethodsRecord.paymentMethodList, +) => { let paymentRequest = ApplePayTypes.getPaymentRequestFromSession(~sessionObj, ~componentName) let message = [ ("applePayButtonClicked", true->JSON.Encode.bool), ("applePayPaymentRequest", paymentRequest), + ( + "isTaxCalculationEnabled", + paymentMethodListValue.is_tax_calculation_enabled->JSON.Encode.bool, + ), ] messageParentWindow(message) } @@ -245,13 +319,16 @@ let useSubmitCallback = (~isWallet, ~sessionObj, ~componentName) => { let areRequiredFieldsEmpty = Recoil.useRecoilValueFromAtom(RecoilAtoms.areRequiredFieldsEmpty) let options = Recoil.useRecoilValueFromAtom(RecoilAtoms.optionAtom) let {localeString} = Recoil.useRecoilValueFromAtom(RecoilAtoms.configAtom) + let paymentMethodListValue = Recoil.useRecoilValueFromAtom(PaymentUtils.paymentMethodListValue) React.useCallback((ev: Window.event) => { if !isWallet { let json = ev.data->safeParse let confirm = json->getDictFromJson->ConfirmType.itemToObjMapper if confirm.doSubmit && areRequiredFieldsValid && !areRequiredFieldsEmpty { - options.readOnly ? () : handleApplePayButtonClicked(~sessionObj, ~componentName) + if !options.readOnly { + handleApplePayButtonClicked(~sessionObj, ~componentName, ~paymentMethodListValue) + } } else if areRequiredFieldsEmpty { postFailedSubmitResponse( ~errortype="validation_error", diff --git a/src/Utilities/PaymentHelpers.res b/src/Utilities/PaymentHelpers.res index 957f0ca11..af404ea10 100644 --- a/src/Utilities/PaymentHelpers.res +++ b/src/Utilities/PaymentHelpers.res @@ -1972,3 +1972,81 @@ let deletePaymentMethod = (~ephemeralKey, ~paymentMethodId, ~logger, ~customPodU JSON.Encode.null->resolve }) } + +let calculateTax = ( + ~apiKey, + ~paymentId, + ~clientSecret, + ~paymentMethodType, + ~shippingAddress, + ~logger, + ~customPodUri, +) => { + open Promise + let endpoint = ApiEndpoint.getApiEndPoint() + let headers = [("Content-Type", "application/json"), ("api-key", apiKey)] + let uri = `${endpoint}/payments/${paymentId}/calculate_tax` + let body = [ + ("client_secret", clientSecret), + ("shipping", shippingAddress), + ("payment_method_type", paymentMethodType), + ] + logApi( + ~optLogger=Some(logger), + ~url=uri, + ~apiLogType=Request, + ~eventName=EXTERNAL_TAX_CALCULATION, + ~logType=INFO, + ~logCategory=API, + ) + fetchApi( + uri, + ~method=#POST, + ~headers=headers->ApiEndpoint.addCustomPodHeader(~customPodUri), + ~bodyStr=body->getJsonFromArrayOfJson->JSON.stringify, + ) + ->then(resp => { + let statusCode = resp->Fetch.Response.status->Int.toString + if statusCode->String.charAt(0) !== "2" { + resp + ->Fetch.Response.json + ->then(data => { + logApi( + ~optLogger=Some(logger), + ~url=uri, + ~data, + ~statusCode, + ~apiLogType=Err, + ~eventName=EXTERNAL_TAX_CALCULATION, + ~logType=ERROR, + ~logCategory=API, + ) + JSON.Encode.null->resolve + }) + } else { + logApi( + ~optLogger=Some(logger), + ~url=uri, + ~statusCode, + ~apiLogType=Response, + ~eventName=EXTERNAL_TAX_CALCULATION, + ~logType=INFO, + ~logCategory=API, + ) + resp->Fetch.Response.json + } + }) + ->catch(err => { + let exceptionMessage = err->formatException + logApi( + ~optLogger=Some(logger), + ~url=uri, + ~apiLogType=NoResponse, + ~eventName=EXTERNAL_TAX_CALCULATION, + ~logType=ERROR, + ~logCategory=API, + ~data=exceptionMessage, + ) + JSON.Encode.null->resolve + }) +} diff --git a/src/Utilities/TaxCalculation.res b/src/Utilities/TaxCalculation.res new file mode 100644 index 000000000..c48b1ae5b --- /dev/null +++ b/src/Utilities/TaxCalculation.res @@ -0,0 +1,27 @@ +open Utils + +type calculateTaxResponse = { + payment_id: string, + net_amount: int, + order_tax_amount: int, + shipping_cost: int, +} + +let taxResponseToObjMapper = dict => { + payment_id: dict->getString("payment_id", ""), + net_amount: dict->getInt("net_amount", 0), + order_tax_amount: dict->getInt("order_tax_amount", 0), + shipping_cost: dict->getInt("shipping_cost", 0), +} + +let calculateTax = (~shippingAddress, ~logger, ~clientSecret, ~publishableKey, ~paymentMethodType) => { + PaymentHelpers.calculateTax( + ~clientSecret=clientSecret->JSON.Encode.string, + ~apiKey=publishableKey, + ~paymentId=clientSecret->getPaymentId, + ~paymentMethodType, + ~shippingAddress, + ~logger, + ~customPodUri="", + ) +} diff --git a/src/orca-loader/Elements.res b/src/orca-loader/Elements.res index ed28ebfad..1785ab672 100644 --- a/src/orca-loader/Elements.res +++ b/src/orca-loader/Elements.res @@ -774,8 +774,9 @@ let make = ( switch ( dict->Dict.get("applePayButtonClicked"), dict->Dict.get("applePayPaymentRequest"), + dict->Dict.get("isTaxCalculationEnabled")->Option.flatMap(JSON.Decode.bool)->Option.getOr(false), ) { - | (Some(val), Some(paymentRequest)) => + | (Some(val), Some(paymentRequest), isTaxCalculationEnabled) => if val->JSON.Decode.bool->Option.getOr(false) { let isDelayedSessionToken = applePayPresent @@ -809,6 +810,9 @@ let make = ( ~logger, ~applePayEvent=Some(applePayEvent), ~callBackFunc, + ~clientSecret, + ~publishableKey, + ~isTaxCalculationEnabled, ) } } else { diff --git a/src/orca-loader/PaymentSessionMethods.res b/src/orca-loader/PaymentSessionMethods.res index 39633ef0d..0130a64d7 100644 --- a/src/orca-loader/PaymentSessionMethods.res +++ b/src/orca-loader/PaymentSessionMethods.res @@ -192,6 +192,8 @@ let getCustomerSavedPaymentMethods = ( ~applePayPresent=Some(applePayTokenRef.contents), ~logger, ~callBackFunc=processPayment, + ~clientSecret, + ~publishableKey, ) } diff --git a/src/orca-log-catcher/OrcaLogger.res b/src/orca-log-catcher/OrcaLogger.res index fa0ca071b..bdcc4e4cb 100644 --- a/src/orca-log-catcher/OrcaLogger.res +++ b/src/orca-log-catcher/OrcaLogger.res @@ -83,6 +83,7 @@ type eventName = | DELETE_SAVED_PAYMENT_METHOD | DELETE_PAYMENT_METHODS_CALL_INIT | DELETE_PAYMENT_METHODS_CALL + | EXTERNAL_TAX_CALCULATION let eventNameToStrMapper = eventName => { switch eventName { @@ -166,6 +167,7 @@ let eventNameToStrMapper = eventName => { | DELETE_SAVED_PAYMENT_METHOD => "DELETE_SAVED_PAYMENT_METHOD" | DELETE_PAYMENT_METHODS_CALL_INIT => "DELETE_PAYMENT_METHODS_CALL_INIT" | DELETE_PAYMENT_METHODS_CALL => "DELETE_PAYMENT_METHODS_CALL" + | EXTERNAL_TAX_CALCULATION => "EXTERNAL_TAX_CALCULATION" } }