Skip to content

Commit

Permalink
Merge pull request #589 from commercelayer/feat/support-new-stripe-pa…
Browse files Browse the repository at this point in the history
…yments

Enable all payments through Stripe
  • Loading branch information
acasazza authored Nov 8, 2024
2 parents 6f30dac + 66c77f1 commit f395864
Show file tree
Hide file tree
Showing 14 changed files with 2,373 additions and 2,540 deletions.
4 changes: 2 additions & 2 deletions lerna.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@
"$schema": "node_modules/lerna/schemas/lerna-schema.json",
"useNx": false,
"npmClient": "pnpm",
"version": "4.15.12",
"version": "4.16.0-beta.2",
"command": {
"version": {
"preid": "beta"
}
}
}
}
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@
},
"devDependencies": {
"@commercelayer/eslint-config-ts-react": "^1.4.5",
"husky": "^9.1.5",
"lerna": "^8.1.3",
"typescript": "^5.4.5"
"husky": "^9.1.6",
"lerna": "^8.1.9",
"typescript": "^5.6.3"
},
"pnpm": {
"overrides": {
Expand Down
28 changes: 14 additions & 14 deletions packages/react-components/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@commercelayer/react-components",
"version": "4.15.12",
"version": "4.16.0-beta.2",
"description": "The Official Commerce Layer React Components",
"main": "lib/cjs/index.js",
"module": "lib/esm/index.js",
Expand Down Expand Up @@ -180,46 +180,46 @@
"dependencies": {
"@adyen/adyen-web": "^5.66.1",
"@commercelayer/organization-config": "^1.4.8",
"@commercelayer/sdk": "^6.13.0",
"@commercelayer/sdk": "^6.25.0",
"@stripe/react-stripe-js": "^2.8.0",
"@stripe/stripe-js": "^4.3.0",
"@tanstack/react-table": "^8.17.3",
"@types/iframe-resizer": "^3.5.13",
"braintree-web": "^3.106.0",
"braintree-web": "^3.111.1",
"frames-react": "^1.1.2",
"iframe-resizer": "^4.4.5",
"jwt-decode": "^4.0.0",
"lodash": "^4.17.21",
"rapid-form": "^2.1.0"
},
"devDependencies": {
"@commercelayer/js-auth": "^6.3.1",
"@faker-js/faker": "^8.4.0",
"@playwright/test": "^1.46.1",
"@commercelayer/js-auth": "^6.7.0",
"@faker-js/faker": "^9.2.0",
"@playwright/test": "^1.48.2",
"@testing-library/dom": "^10.4.0",
"@testing-library/react": "^16.0.0",
"@types/braintree-web": "^3.96.12",
"@types/lodash": "^4.17.7",
"@types/node": "^22.5.0",
"@types/lodash": "^4.17.13",
"@types/node": "^22.9.0",
"@types/prop-types": "^15.7.12",
"@types/react": "^18.3.4",
"@types/react-test-renderer": "^18.0.7",
"@types/react-window": "^1.8.8",
"@vitejs/plugin-react": "^4.3.1",
"@vitest/coverage-v8": "^2.0.5",
"eslint": "~8.57.0",
"jsdom": "^24.1.1",
"@vitest/coverage-v8": "^2.1.4",
"eslint": "~9.14.0",
"jsdom": "^25.0.1",
"minimize-js": "^1.4.0",
"msw": "^2.3.5",
"msw": "^2.6.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-test-renderer": "^18.2.0",
"tsc-alias": "^1.8.8",
"tslib": "^2.6.3",
"typescript": "^5.5.4",
"vite": "^5.4.2",
"vite-tsconfig-paths": "^5.0.1",
"vitest": "^2.0.5"
"vite-tsconfig-paths": "^5.1.0",
"vitest": "^2.1.4"
},
"peerDependencies": {
"react": ">=18.0.0"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import { type ReactNode, useContext, useEffect, useReducer } from 'react'
import addressReducer, {
addressInitialState,
type AddressResource,
type AddressSchema,
setAddressErrors,
type SetAddressParams,
setCloneAddress,
Expand All @@ -16,6 +15,7 @@ import { type BaseError } from '#typings/errors'
import OrderContext from '#context/OrderContext'
import CommerceLayerContext from '#context/CommerceLayerContext'
import { setCustomerOrderParam } from '#utils/localStorage'
import { type TCustomerAddress } from '#reducers/CustomerReducer'

interface Props {
children: ReactNode
Expand Down Expand Up @@ -101,7 +101,7 @@ export function AddressesContainer(props: Props): JSX.Element {
currentErrors: state.errors
})
},
setAddress: (params: SetAddressParams<AddressSchema>) => {
setAddress: (params: SetAddressParams<TCustomerAddress>) => {
defaultAddressContext.setAddress({ ...params, dispatch })
},
saveAddresses: async (params: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import isFunction from 'lodash/isFunction'
import { type TCustomerAddress } from '#reducers/CustomerReducer'
import type { Order } from '@commercelayer/sdk'
import { validateValue } from '#utils/validateFormFields'
import { formCleaner } from '#utils/formCleaner'

interface TOnClick {
success: boolean
Expand Down Expand Up @@ -146,8 +147,8 @@ export function SaveAddressesButton(props: Props): JSX.Element {
}
case createCustomerAddress != null: {
const address = invertAddresses
? { ...shippingAddress }
: { ...billingAddress }
? { ...formCleaner(shippingAddress) }
: { ...formCleaner(billingAddress) }
if (addressId) address.id = addressId
void createCustomerAddress(address as TCustomerAddress)
response = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { type AddressField } from '#reducers/AddressReducer'
import { type AddressCountrySelectName, type AddressInputName } from '#typings'
import OrderContext from '#context/OrderContext'
import { isEmptyStates } from '#utils/countryStateCity'
import type { Address } from '@commercelayer/sdk'
import { type TCustomerAddress } from '#reducers/CustomerReducer'

interface Props extends Omit<JSX.IntrinsicElements['form'], 'onSubmit'> {
children: ReactNode
Expand Down Expand Up @@ -85,7 +85,10 @@ export function CustomerAddressForm(props: Props): JSX.Element {
}
}
}
setAddress({ values: values as Address, resource: 'billing_address' })
setAddress({
values: values as TCustomerAddress,
resource: 'billing_address'
})
}
if (
reset &&
Expand All @@ -109,7 +112,7 @@ export function CustomerAddressForm(props: Props): JSX.Element {
[name.replace('billing_address_', '')]: value
}
setAddress({
values: { ...(values as Address), ...field },
values: { ...(values as TCustomerAddress), ...field },
resource: 'billing_address'
})
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import OrderContext from '#context/OrderContext'
import getCardDetails from '#utils/getCardDetails'
import type { BaseError } from '#typings/errors'
import type { Order } from '@commercelayer/sdk'
import { checkPaymentIntent } from '#utils/stripe/retrievePaymentIntent'

interface ChildrenProps extends Omit<Props, 'children'> {
/**
Expand Down Expand Up @@ -126,6 +127,7 @@ export function PlaceOrderButton(props: Props): JSX.Element {
paymentSource
])
useEffect(() => {
// PayPal redirect flow
if (
paymentType === 'paypal_payments' &&
options?.paypalPayerId &&
Expand All @@ -137,19 +139,57 @@ export function PlaceOrderButton(props: Props): JSX.Element {
}
}, [options?.paypalPayerId, paymentType])
useEffect(() => {
// Stripe redirect flow
if (
paymentType === 'stripe_payments' &&
['succeeded', 'pending'].includes(
options?.stripe?.redirectStatus ?? ''
) &&
options?.stripe?.paymentIntentClientSecret &&
// @ts-expect-error no type
order?.payment_source?.publishable_key &&
order?.status &&
['draft', 'pending'].includes(order?.status) &&
autoPlaceOrder
) {
void handleClick()
// @ts-expect-error no type
const publicApiKey = order?.payment_source?.publishable_key
const paymentIntentClientSecret =
options?.stripe?.paymentIntentClientSecret

const getPaymentIntent = async (): Promise<void> => {
const paymentIntentResult = await checkPaymentIntent({
publicApiKey,
paymentIntentClientSecret
})
switch (paymentIntentResult.status) {
case 'valid':
void handleClick()
break
case 'processing':
// Set a timeout to check the payment intent status again
setTimeout(() => {
void getPaymentIntent()
}, 1000)
break
case 'invalid':
setPaymentMethodErrors([
{
code: 'PAYMENT_INTENT_AUTHENTICATION_FAILURE',
resource: 'payment_methods',
field: currentPaymentMethodType,
message: paymentIntentResult.message
}
])
break
}
}
void getPaymentIntent()
}
}, [options?.stripe?.redirectStatus, paymentType])
}, [
options?.stripe?.paymentIntentClientSecret != null,
paymentType != null,
order?.payment_source != null
])
useEffect(() => {
// Adyen redirect flow
if (order?.status != null && ['draft', 'pending'].includes(order?.status)) {
const resultCode =
// @ts-expect-error no type
Expand Down Expand Up @@ -230,6 +270,7 @@ export function PlaceOrderButton(props: Props): JSX.Element {
order?.payment_source?.payment_response?.resultCode
])
useEffect(() => {
// Checkout.com redirect flow
if (
paymentType === 'checkout_com_payments' &&
options?.checkoutCom?.session_id &&
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,10 +116,12 @@ function StripePaymentForm({
state: billingInfo?.state_code
}
}
const url = new URL(window.location.href)
const cleanUrl = `${url.origin}${url.pathname}?accessToken=${url.searchParams.get('accessToken')}`
const { error } = await stripe.confirmPayment({
elements,
confirmParams: {
return_url: window.location.href,
return_url: cleanUrl,
payment_method_data: {
billing_details: billingDetails
}
Expand Down
4 changes: 2 additions & 2 deletions packages/react-components/src/context/AddressContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@ import {
setAddress,
type AddressResource,
type saveAddresses,
ICustomerAddress
type ICustomerAddress
} from '#reducers/AddressReducer'
import { type BaseError } from '#typings/errors'

type DefaultContext = {
saveAddresses?: (params: {
customerEmail?: string,
customerEmail?: string
customerAddress?: ICustomerAddress
}) => ReturnType<typeof saveAddresses>
setCloneAddress: (id: string, resource: AddressResource) => void
Expand Down
19 changes: 8 additions & 11 deletions packages/react-components/src/reducers/AddressReducer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,7 @@ import baseReducer from '#utils/baseReducer'
import { type Dispatch } from 'react'
import { type CodeErrorType, type BaseError } from '#typings/errors'
import { type CommerceLayerConfig } from '#context/CommerceLayerContext'
import {
type OrderUpdate,
type Address,
type AddressCreate,
type Order
} from '@commercelayer/sdk'
import { type OrderUpdate, type Address, type Order } from '@commercelayer/sdk'
import getSdk from '#utils/getSdk'
import { type updateOrder } from './OrderReducer'
import camelCase from 'lodash/camelCase'
Expand Down Expand Up @@ -86,7 +81,7 @@ export type AddressSchema = Omit<
export interface AddressActionPayload {
errors: BaseError[]
billing_address: TCustomerAddress
shipping_address: AddressCreate & Record<string, string | null | undefined>
shipping_address: TCustomerAddress
shipToDifferentAddress: boolean
billingAddressId: string
shippingAddressId: string
Expand All @@ -112,7 +107,7 @@ export type SetAddressErrors = <V extends BaseError[]>(args: {
currentErrors?: V
}) => void

export interface SetAddressParams<V extends AddressSchema> {
export interface SetAddressParams<V extends TCustomerAddress> {
values: V
resource: AddressResource
dispatch?: Dispatch<AddressAction>
Expand Down Expand Up @@ -142,7 +137,7 @@ export const setAddressErrors: SetAddressErrors = ({
})
}

export function setAddress<V extends AddressSchema>({
export function setAddress<V extends TCustomerAddress>({
values,
resource,
dispatch
Expand Down Expand Up @@ -206,13 +201,15 @@ export async function saveAddresses({
const {
shipToDifferentAddress,
invertAddresses,
billing_address: billingAddress,
shipping_address: shippingAddress,
billing_address: billingAddressForm,
shipping_address: shippingAddressForm,
billingAddressId,
shippingAddressId
} = state
try {
const sdk = getSdk(config)
const billingAddress = formCleaner(billingAddressForm)
const shippingAddress = formCleaner(shippingAddressForm)
if (order) {
let orderAttributes: OrderUpdate | null = null
const billingAddressCloneId =
Expand Down
7 changes: 6 additions & 1 deletion packages/react-components/src/reducers/PlaceOrderReducer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,12 @@ export interface PlaceOrderOptions {
session_id: string
}
stripe?: {
redirectStatus: string
/**
* @deprecated
* Use `paymentIntentClientSecret` instead
*/
redirectStatus?: string
paymentIntentClientSecret: string
}
}

Expand Down
14 changes: 10 additions & 4 deletions packages/react-components/src/utils/formCleaner.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,25 @@
/* eslint-disable @typescript-eslint/no-dynamic-delete */
import { type AddressSchema } from '#reducers/AddressReducer'
import { type TCustomerAddress } from '#reducers/CustomerReducer'

export function formCleaner(address: AddressSchema): AddressSchema {
type CombinedAddressType = TCustomerAddress | undefined

export function formCleaner(address: CombinedAddressType): CombinedAddressType {
if (!address) {
return address
}
Object.keys(address).forEach((key) => {
const keyCleaned = key
.replace('shipping_address_', '')
.replace('billing_address_', '')
const isNotCleaned =
key.startsWith('shipping_address_') || key.startsWith('billing_address_')
if (isNotCleaned) {
// @ts-expect-error type error
address[keyCleaned] = address[key]
// @ts-expect-error type error
delete address[key]
}
if (keyCleaned === 'save_to_customer_book') {
delete address[keyCleaned]
}
})
return address
}
Loading

0 comments on commit f395864

Please sign in to comment.