Skip to content

Commit

Permalink
feat(applepay): calculate tax amount dynamically while changing shipp…
Browse files Browse the repository at this point in the history
…ing address in apple pay sdk (#610)

Co-authored-by: Sagnik Mitra <[email protected]>
Co-authored-by: Pritish Budhiraja <[email protected]>
  • Loading branch information
3 people authored Sep 13, 2024
1 parent 0f60c12 commit 0804823
Show file tree
Hide file tree
Showing 10 changed files with 207 additions and 5 deletions.
2 changes: 1 addition & 1 deletion src/Components/SavedMethods.res
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
2 changes: 1 addition & 1 deletion src/Payments/ApplePay.res
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,7 @@ let make = (~sessionObj: option<JSON.t>, ~walletOptions, ~paymentType: CardTheme
~isManualRetryEnabled,
)
} else {
ApplePayHelpers.handleApplePayButtonClicked(~sessionObj, ~componentName)
ApplePayHelpers.handleApplePayButtonClicked(~sessionObj, ~componentName, ~paymentMethodListValue)
}
} else {
let bodyDict = PaymentBody.applePayRedirectBody(~connectors)
Expand Down
3 changes: 3 additions & 0 deletions src/Payments/PaymentMethodsRecord.res
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand All @@ -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 => {
Expand Down Expand Up @@ -1025,6 +1027,7 @@ let itemToObjMapper = dict => {
"collect_billing_details_from_wallets",
true,
),
is_tax_calculation_enabled: getBool(dict,"is_tax_calculation_enabled",false)
}
}

Expand Down
9 changes: 9 additions & 0 deletions src/Types/ApplePayTypes.res
Original file line number Diff line number Diff line change
Expand Up @@ -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<lineItem>}
type innerSession
type session = {
begin: unit => unit,
Expand All @@ -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,
Expand Down
81 changes: 79 additions & 2 deletions src/Utilities/ApplePayHelpers.res
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
open ApplePayTypes
open Utils
open TaxCalculation

let processPayment = (
~bodyArr,
Expand Down Expand Up @@ -75,7 +76,11 @@ let startApplePaySession = (
~applePayEvent: option<Types.event>=None,
~callBackFunc,
~resolvePromise: option<Core__JSON.t => unit>=None,
~clientSecret,
~publishableKey,
~isTaxCalculationEnabled=false,
) => {
open Promise
let ssn = applePaySession(3, paymentRequest)
switch applePaySessionRef.contents->Nullable.toOption {
| Some(session) =>
Expand All @@ -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<lineItem> = [
{
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
Expand Down Expand Up @@ -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)
}
Expand All @@ -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",
Expand Down
78 changes: 78 additions & 0 deletions src/Utilities/PaymentHelpers.res
Original file line number Diff line number Diff line change
Expand Up @@ -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
})
}
27 changes: 27 additions & 0 deletions src/Utilities/TaxCalculation.res
Original file line number Diff line number Diff line change
@@ -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="",
)
}
6 changes: 5 additions & 1 deletion src/orca-loader/Elements.res
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -809,6 +810,9 @@ let make = (
~logger,
~applePayEvent=Some(applePayEvent),
~callBackFunc,
~clientSecret,
~publishableKey,
~isTaxCalculationEnabled,
)
}
} else {
Expand Down
2 changes: 2 additions & 0 deletions src/orca-loader/PaymentSessionMethods.res
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,8 @@ let getCustomerSavedPaymentMethods = (
~applePayPresent=Some(applePayTokenRef.contents),
~logger,
~callBackFunc=processPayment,
~clientSecret,
~publishableKey,
)
}

Expand Down
2 changes: 2 additions & 0 deletions src/orca-log-catcher/OrcaLogger.res
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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"
}
}

Expand Down

0 comments on commit 0804823

Please sign in to comment.