Skip to content
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

wip: easypost integration #175

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions graphql/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -42,4 +42,5 @@ type Mutation {
comment: ReturnRequestCommentInput
refundData: RefundDataInput
): ReturnRequestResponse @withUserProfile
sendReturnLabel(requestId: String!, labelUrl: String!): Boolean
}
3 changes: 2 additions & 1 deletion manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@
"vtex.css-handles": "0.x",
"vtex.easypost": "0.x",
"vtex.tenant-graphql": "0.x",
"vtex.catalog-graphql": "1.x"
"vtex.catalog-graphql": "1.x",
"vtex.apps-graphql": "3.x"
},
"builders": {
"admin": "0.x",
Expand Down
7 changes: 7 additions & 0 deletions messages/context.json
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,13 @@
"store/return-app.return-order-details.pickup-address.drop-off-points.dropdown.placehoder": "Select a drop off point",
"store/return-app.return-order-details.pickup-address.drop-off-points.dropdown.error": "There was an error loading the drop off points",
"admin/return-app.return-request-details.localized-product-name.tooltip": "Tooltip for localized product names",
"admin/return-app.return-request-details.return-label.modal-title": "Are you sure you want to create a return shipping label?",
"admin/return-app.return-request-details.return-label.modal-message": "This action is irreversible. The created label will be sent via email to the customer.",
"admin/return-app.return-request-details.return-label.create-return-label": "Create return label",
"admin/return-app.return-request-details.return-label.see-return-label": "See return label",
"admin/return-app.return-request-details.return-label-info.tooltip": "Here you can create a return shipping label, this label will be send by email to the customer and he can paste it on the package. For this integration to work properly you must install and configure vtex.easypost.",
edyespinal marked this conversation as resolved.
Show resolved Hide resolved
"admin/return-app.return-request-details.return-label.alert.success": "The return label was created successfully",
"admin/return-app.return-request-details.return-label.alert.error": "There was an error creating the return label, please try again.",
"admin/return-app.settings.section.payment-options.refund-method-strategy.checkbox.label": "Automatically refund requests",
"admin/return-app.settings.section.payment-options.refund-method-strategy.checkbox.description": "Automatically create the return invoice in the OMS for the requests",
"return-app.return-request-details.payent-method.refund-option.refund-process": "Refund process: {automaticallyRefundPaymentMethod, select, true{Automatically} false{Manually}}",
Expand Down
15 changes: 11 additions & 4 deletions messages/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -315,11 +315,18 @@
"admin/return-app.settings.modal-warning.third-paragraph": "If you click Edit, the maximum days for a return will be reduced from <b>{maxDays}</b> to <b>{customMaxDays}</b>. This is the Max Days shown in your custom return options.",
"admin/return-app.settings.modal-warning.confirm": "Edit",
"admin/return-app.settings.modal-warning.cancel": "Cancel",
"store/return-app.return-order-details.pickup-address.drop-off-points": "Select a drop-off point",
"store/return-app.return-order-details.pickup-address.drop-off-points.tooltip": "You can return your items in one of our selected drop-off points",
"store/return-app.return-order-details.pickup-address.drop-off-points.dropdown.placehoder": "Select a drop-off point",
"store/return-app.return-order-details.pickup-address.drop-off-points.dropdown.error": "There was an error loading the drop-off points",
"store/return-app.return-order-details.pickup-address.drop-off-points": "Select a drop off point",
"store/return-app.return-order-details.pickup-address.drop-off-points.tooltip": "You can return your items in one of our selected drop off points",
"store/return-app.return-order-details.pickup-address.drop-off-points.dropdown.placehoder": "Select a drop off point",
"store/return-app.return-order-details.pickup-address.drop-off-points.dropdown.error": "There was an error loading the drop off points",
"admin/return-app.return-request-details.localized-product-name.tooltip": "Original item name from the order",
"admin/return-app.return-request-details.return-label.modal-title": "Are you sure you want to create a return shipping label?",
"admin/return-app.return-request-details.return-label.modal-message": "This action is irreversible. The created label will be sent via email to the customer.",
"admin/return-app.return-request-details.return-label.create-return-label": "Create return label",
"admin/return-app.return-request-details.return-label.see-return-label": "See return label",
"admin/return-app.return-request-details.return-label-info.tooltip": "Here you can create a return shipping label, this label will be send by email to the customer and he can paste it on the package. For this integration to work properly you must install and configure vtex.easypost.",
edyespinal marked this conversation as resolved.
Show resolved Hide resolved
"admin/return-app.return-request-details.return-label.alert.success": "The return label was created successfully",
"admin/return-app.return-request-details.return-label.alert.error": "There was an error creating the return label, please try again.",
"admin/return-app.settings.section.payment-options.refund-method-strategy.checkbox.label": "Automatically refund requests",
"admin/return-app.settings.section.payment-options.refund-method-strategy.checkbox.description": "Automatically create a return invoice in OMS for the requests",
"return-app.return-request-details.payent-method.refund-option.refund-process": "Refund process: {automaticallyRefundPaymentMethod, select, true{Automatic} false{Manual}}",
Expand Down
5 changes: 3 additions & 2 deletions node/clients/mail.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import type {
StatusUpdateMailData,
ConfirmationMailData,
Template,
ReturnLabelMailData,
} from '../typings/mailClient'

const MAIL_SERVICE_PATH = '/api/mail-service/pvt/sendmail'
Expand All @@ -21,7 +22,7 @@ export class MailClient extends JanusClient {
}

public sendMail(
mailData: StatusUpdateMailData | ConfirmationMailData
mailData: StatusUpdateMailData | ConfirmationMailData | ReturnLabelMailData
): Promise<string> {
return this.http.post(MAIL_SERVICE_PATH, mailData, {
metric: 'mail-post-send',
Expand All @@ -34,7 +35,7 @@ export class MailClient extends JanusClient {
})
}

public publishTemplate(template: Template): Promise<any> {
public publishTemplate(template: Template): Promise<unknown> {
return this.http.post(TEMPLATE_RENDER_PATH, template, {
headers: {
...this.options?.headers,
Expand Down
1 change: 1 addition & 0 deletions node/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"@vtex/tsconfig": "^0.6.0",
"tslint": "^6.1.3",
"tslint-config-vtex": "^2.1.0",
"vtex.apps-graphql": "http://vtex.vtexassets.com/_v/public/typings/v1/[email protected]/public/@types/vtex.apps-graphql",
"vtex.catalog-graphql": "http://vtex.vtexassets.com/_v/public/typings/v1/[email protected]/public/@types/vtex.catalog-graphql",
"vtex.css-handles": "http://vtex.vtexassets.com/_v/public/typings/v1/[email protected]/public/@types/vtex.css-handles",
"vtex.easypost": "http://vtex.vtexassets.com/_v/public/typings/v1/[email protected]/public/@types/vtex.easypost",
Expand Down
2 changes: 2 additions & 0 deletions node/resolvers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,12 @@ import { returnRequestList } from './returnRequestList'
import { ReturnRequestResponse } from './ReturnRequestResponse'
import { updateReturnRequestStatus } from './updateReturnRequestStatus'
import { nearestPickupPoints } from './nearestPickupPoints'
import { sendReturnLabel } from './sendReturnLabel'

export const mutations = {
createReturnRequest,
updateReturnRequestStatus,
sendReturnLabel,
...settingsMutation,
}

Expand Down
16 changes: 16 additions & 0 deletions node/resolvers/sendReturnLabel.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { sendReturnLabelService } from '../services/sendReturnLabelService'

interface QuerySendReturnLabelArgs {
requestId: string
labelUrl: string
}

export const sendReturnLabel = async (
_: unknown,
args: QuerySendReturnLabelArgs,
ctx: Context
) => {
const { requestId, labelUrl } = args

return sendReturnLabelService(ctx, requestId, labelUrl)
}
10 changes: 4 additions & 6 deletions node/services/createReturnRequestService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,7 @@ import type { ReturnRequestCreated, ReturnRequestInput } from 'vtex.return-app'
import { UserInputError, ResolverError } from '@vtex/api'
import type { DocumentResponse } from '@vtex/clients'

import {
SETTINGS_PATH,
OMS_RETURN_REQUEST_CONFIRMATION,
} from '../utils/constants'
import { SETTINGS_PATH } from '../utils/constants'
import { isUserAllowed } from '../utils/isUserAllowed'
import { canOrderBeReturned } from '../utils/canOrderBeReturned'
import { canReturnAllItems } from '../utils/canReturnAllItems'
Expand All @@ -17,6 +14,7 @@ import { createRefundableTotals } from '../utils/createRefundableTotals'
import { OMS_RETURN_REQUEST_CONFIRMATION_TEMPLATE } from '../utils/templates'
import type { ConfirmationMailData } from '../typings/mailClient'
import { getCustomerEmail } from '../utils/getCostumerEmail'
import { templateName } from '../utils/emailTemplates'
import { validateItemCondition } from '../utils/validateItemCondition'

export const createReturnRequestService = async (
Expand Down Expand Up @@ -285,7 +283,7 @@ export const createReturnRequestService = async (
// We add a try/catch here so we avoid sending an error to the browser only if the email fails.
try {
const templateExists = await mail.getTemplate(
OMS_RETURN_REQUEST_CONFIRMATION(locale)
templateName('confirmation', locale)
)

if (!templateExists) {
Expand All @@ -305,7 +303,7 @@ export const createReturnRequestService = async (
} = shippingData

const mailData: ConfirmationMailData = {
templateName: OMS_RETURN_REQUEST_CONFIRMATION(locale),
templateName: templateName('confirmation', locale),
jsonData: {
data: {
status: 'new',
Expand Down
96 changes: 96 additions & 0 deletions node/services/sendReturnLabelService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import { ResolverError } from '@vtex/api'
import type { ReturnRequest } from 'vtex.return-app'

import type { ReturnLabelMailData } from '../typings/mailClient'
import { templateName } from '../utils/emailTemplates'
import { OMS_RETURN_REQUEST_LABEL_TEMPLATE } from '../utils/templates'
import { formatRequestToPartialUpdate } from './updateRequestStatusService'

export const sendReturnLabelService = async (
ctx: Context,
requestId: string,
labelUrl: string
) => {
const {
clients: { mail, returnRequest: returnRequestClient },
vtex: { logger },
} = ctx

if (!requestId || !labelUrl) {
throw new ResolverError(
'The requestId or the labelUrl was not provided',
400
)
}

const returnRequest = (await returnRequestClient.get(requestId, [
'_all',
])) as ReturnRequest

const {
pickupReturnData,
cultureInfoData: { locale },
} = returnRequest

const updatedPickupReturnData = {
...pickupReturnData,
labelUrl,
}

const updatedRequest = {
...formatRequestToPartialUpdate(returnRequest),
pickupReturnData: updatedPickupReturnData,
}

try {
await returnRequestClient.update(requestId, updatedRequest)
} catch (error) {
const mdValidationErrors = error?.response?.data?.errors[0]?.errors

const errorMessageString = mdValidationErrors
? JSON.stringify(
{
message: 'Schema Validation error',
errors: mdValidationErrors,
},
null,
2
)
: error.message

throw new ResolverError(errorMessageString, error.response?.status || 500)
}

try {
const templateExists = await mail.getTemplate(templateName('label', locale))

if (!templateExists) {
await mail.publishTemplate(OMS_RETURN_REQUEST_LABEL_TEMPLATE(locale))
}

const { customerProfileData } = updatedRequest

const mailData: ReturnLabelMailData = {
templateName: templateName('label', locale),
jsonData: {
data: {
name: customerProfileData?.name ?? '',
edyespinal marked this conversation as resolved.
Show resolved Hide resolved
DocumentId: requestId,
email: customerProfileData?.email ?? '',
edyespinal marked this conversation as resolved.
Show resolved Hide resolved
labelUrl,
},
},
}

await mail.sendMail(mailData)
} catch (error) {
logger.warn({
message: `Failed to send email for return request ${requestId}`,
error,
})

return false
}

return true
}
8 changes: 4 additions & 4 deletions node/services/updateRequestStatusService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,14 @@ import { validateStatusUpdate } from '../utils/validateStatusUpdate'
import { createOrUpdateStatusPayload } from '../utils/createOrUpdateStatusPayload'
import { createRefundData } from '../utils/createRefundData'
import { handleRefund } from '../utils/handleRefund'
import { OMS_RETURN_REQUEST_STATUS_UPDATE } from '../utils/constants'
import { OMS_RETURN_REQUEST_STATUS_UPDATE_TEMPLATE } from '../utils/templates'
import type { StatusUpdateMailData } from '../typings/mailClient'
import { templateName } from '../utils/emailTemplates'

// A partial update on MD requires all required field to be sent. https://vtex.slack.com/archives/C8EE14F1C/p1644422359807929
// And the request to update fails when we pass the auto generated ones.
// If any new field is added to the ReturnRequest as required, it has to be added here too.
const formatRequestToPartialUpdate = (
export const formatRequestToPartialUpdate = (
request: ReturnRequest
): ReturnRequest => {
const {
Expand Down Expand Up @@ -232,7 +232,7 @@ export const updateRequestStatusService = async (
// We add a try/catch here so we avoid sending an error to the browser only if the email fails.
try {
const templateExists = await mail.getTemplate(
OMS_RETURN_REQUEST_STATUS_UPDATE(cultureInfoData?.locale)
templateName('status-update', cultureInfoData?.locale)
edyespinal marked this conversation as resolved.
Show resolved Hide resolved
)

if (!templateExists) {
Expand All @@ -251,7 +251,7 @@ export const updateRequestStatusService = async (
} = updatedRequest

const mailData: StatusUpdateMailData = {
templateName: OMS_RETURN_REQUEST_STATUS_UPDATE(cultureInfoData?.locale),
templateName: templateName('status-update', cultureInfoData?.locale),
edyespinal marked this conversation as resolved.
Show resolved Hide resolved
jsonData: {
data: {
status: updatedStatus,
Expand Down
85 changes: 52 additions & 33 deletions node/typings/mailClient.d.ts
Original file line number Diff line number Diff line change
@@ -1,45 +1,64 @@
import type { ReturnRequestItem, Status } from 'vtex.return-app'
import type {
ReturnRequest,
RefundStatusData,
ReturnRequestItem,
Status,
} from 'vtex.return-app'

export type ReturnRequestConfirmation = string
interface MailData<DataType> {
templateName: string
jsonData: DataType
}

export type ReturnRequestStatusUpdate = string
interface ConfirmationData {
data: {
status: Status['new']
name: string
DocumentId: string
email: string
phoneNumber: string
country: string
locality: string
address: string
paymentMethod: string
}
products: ReturnRequestItem[]
refundStatusData: RefundStatusData[]
}

export interface ConfirmationMailData {
templateName: ReturnRequestConfirmation
jsonData: {
data: {
status: Status['new']
name: string
DocumentId: string
email: string
phoneNumber: string
country: string
locality: string
address: string
paymentMethod: string
}
products: ReturnRequestItem[]
refundStatusData: ReturnRequest['refundStatusData']
interface StatusUpdateData {
data: {
status: Status
name: string
DocumentId: string
email: string
paymentMethod: string
iban: string
refundedAmount: number
edyespinal marked this conversation as resolved.
Show resolved Hide resolved
}
products: ReturnRequest['items']
refundStatusData: RefundStatusData[]
}

export interface StatusUpdateMailData {
templateName: ReturnRequestStatusUpdate
jsonData: {
data: {
status: Status
name: string
DocumentId: string
email: string
paymentMethod: string
iban: string
refundedAmount: number
}
products: ReturnRequest['items']
refundStatusData: ReturnRequest['refundStatusData']
interface ReturnLabelData {
data: {
name: string
DocumentId: string
email: string
labelUrl: string
}
}

export type TemplateName = 'confirmation' | 'status-update' | 'label'

export type TemplateFriendlyName = 'Confirmation' | 'Status Update' | 'Label'

export type ConfirmationMailData = MailData<ConfirmationData>

export type StatusUpdateMailData = MailData<StatusUpdateData>

export type ReturnLabelMailData = MailData<ReturnLabelData>

export interface Template {
AccountId: string | null
AccountName: string | null
Expand Down
Loading