From e17d80fc36b7c32bd6e8ba648013839596f51ad4 Mon Sep 17 00:00:00 2001
From: Ian Krieger <48930920+IanKrieger@users.noreply.github.com>
Date: Mon, 17 Jul 2023 08:39:11 -0400
Subject: [PATCH] feat: allow for BAT payments via hosted checkout (#827)
* wip: allow for BAT payments via hosted checkout
* Allow for change of payment method in draft
* fix: seperate payment method
* fix: redirect to settings
---
src/checkout/hooks/useCreatePaymentSession.ts | 3 ++
src/graphql/campaign.generated.tsx | 3 ++
src/graphql/campaign.graphql | 1 +
src/graphql/types.ts | 1 +
src/user/library/index.ts | 2 +
src/user/views/adsManager/types/index.ts | 3 ++
.../components/campaign/BudgetSettings.tsx | 13 ++++++
.../campaign/fields/BudgetField.tsx | 34 ++++------------
.../campaign/fields/PaymentMethodField.tsx | 40 +++++++++++++++++++
.../completionForm/CompletionForm.tsx | 13 ++----
.../advanced/components/form/EditCampaign.tsx | 8 +---
.../components/form/components/BaseForm.tsx | 3 +-
.../form/components/PaymentButton.tsx | 6 +--
13 files changed, 83 insertions(+), 47 deletions(-)
create mode 100644 src/user/views/adsManager/views/advanced/components/campaign/BudgetSettings.tsx
create mode 100644 src/user/views/adsManager/views/advanced/components/campaign/fields/PaymentMethodField.tsx
diff --git a/src/checkout/hooks/useCreatePaymentSession.ts b/src/checkout/hooks/useCreatePaymentSession.ts
index a3368c1c..66f7c377 100644
--- a/src/checkout/hooks/useCreatePaymentSession.ts
+++ b/src/checkout/hooks/useCreatePaymentSession.ts
@@ -1,9 +1,11 @@
import { createPaymentSession } from "checkout/lib";
import { useCallback, useState } from "react";
import { useAdvertiser } from "auth/hooks/queries/useAdvertiser";
+import { useHistory } from "react-router-dom";
export function useCreatePaymentSession() {
const [loading, setLoading] = useState(false);
+ const history = useHistory();
const { advertiser } = useAdvertiser();
const replaceSession = useCallback(async (campaignId: string) => {
@@ -15,6 +17,7 @@ export function useCreatePaymentSession() {
.catch((e) => {
alert("Unable to create payment session. Please try again.");
setLoading(false);
+ history.push(`/user/main/adsmanager/advanced/${campaignId}/settings`);
});
}, []);
diff --git a/src/graphql/campaign.generated.tsx b/src/graphql/campaign.generated.tsx
index 69bf2fdc..0cf619a8 100644
--- a/src/graphql/campaign.generated.tsx
+++ b/src/graphql/campaign.generated.tsx
@@ -28,6 +28,7 @@ export type CampaignFragment = {
paymentType: Types.PaymentType;
dayProportion?: number | null;
stripePaymentId?: string | null;
+ hasPaymentIntent?: boolean | null;
geoTargets?: Array<{
__typename?: "Geocode";
code: string;
@@ -196,6 +197,7 @@ export type LoadCampaignQuery = {
paymentType: Types.PaymentType;
dayProportion?: number | null;
stripePaymentId?: string | null;
+ hasPaymentIntent?: boolean | null;
geoTargets?: Array<{
__typename?: "Geocode";
code: string;
@@ -369,6 +371,7 @@ export const CampaignFragmentDoc = gql`
dayProportion
stripePaymentId
paymentType
+ hasPaymentIntent
geoTargets {
code
name
diff --git a/src/graphql/campaign.graphql b/src/graphql/campaign.graphql
index 7a4bf608..1ca98e5d 100644
--- a/src/graphql/campaign.graphql
+++ b/src/graphql/campaign.graphql
@@ -22,6 +22,7 @@ fragment Campaign on Campaign {
dayProportion
stripePaymentId
paymentType
+ hasPaymentIntent
geoTargets {
code
name
diff --git a/src/graphql/types.ts b/src/graphql/types.ts
index e79df52a..bea17794 100644
--- a/src/graphql/types.ts
+++ b/src/graphql/types.ts
@@ -337,6 +337,7 @@ export type NotificationPayloadInput = {
export enum PaymentType {
ManualBat = "MANUAL_BAT",
Netsuite = "NETSUITE",
+ Radom = "RADOM",
Stripe = "STRIPE",
}
diff --git a/src/user/library/index.ts b/src/user/library/index.ts
index f95cdde1..b8f146c8 100644
--- a/src/user/library/index.ts
+++ b/src/user/library/index.ts
@@ -160,6 +160,7 @@ export function editCampaignValues(
};
}),
advertiserId,
+ hasPaymentIntent: campaign.hasPaymentIntent ?? false,
creatives: creativeList(ads).map((a) => a.id!),
newCreative: initialCreative,
isCreating: false,
@@ -219,6 +220,7 @@ export function transformEditForm(
startAt: form.startAt,
state: form.state,
type: form.type,
+ paymentType: form.paymentType,
adSets: form.adSets.map((adSet) => ({
id: adSet.id,
segments: adSet.segments.map((v) => ({ code: v.code, name: v.name })),
diff --git a/src/user/views/adsManager/types/index.ts b/src/user/views/adsManager/types/index.ts
index 2cc37ba9..c248e8a6 100644
--- a/src/user/views/adsManager/types/index.ts
+++ b/src/user/views/adsManager/types/index.ts
@@ -32,7 +32,9 @@ export type CampaignForm = {
price: number;
billingType: Billing;
pacingStrategy: CampaignPacingStrategies;
+ hasPaymentIntent: boolean;
stripePaymentId?: string | null;
+ radomPaymentId?: string | null;
paymentType: PaymentType;
};
@@ -109,6 +111,7 @@ export const initialCampaign = (advertiser: IAdvertiser): CampaignForm => {
validateStart: true,
isCreating: false,
budget: MIN_PER_CAMPAIGN,
+ hasPaymentIntent: false,
currency: "USD",
dailyBudget: MIN_PER_CAMPAIGN,
dailyCap: 1,
diff --git a/src/user/views/adsManager/views/advanced/components/campaign/BudgetSettings.tsx b/src/user/views/adsManager/views/advanced/components/campaign/BudgetSettings.tsx
new file mode 100644
index 00000000..b1036228
--- /dev/null
+++ b/src/user/views/adsManager/views/advanced/components/campaign/BudgetSettings.tsx
@@ -0,0 +1,13 @@
+import React from "react";
+import { BudgetField } from "user/views/adsManager/views/advanced/components/campaign/fields/BudgetField";
+import { PaymentMethodField } from "user/views/adsManager/views/advanced/components/campaign/fields/PaymentMethodField";
+
+export function BudgetSettings(props: { isEdit: boolean }) {
+ return (
+ <>
+
+
+
+ >
+ );
+}
diff --git a/src/user/views/adsManager/views/advanced/components/campaign/fields/BudgetField.tsx b/src/user/views/adsManager/views/advanced/components/campaign/fields/BudgetField.tsx
index 4387bf4a..e2818c5c 100644
--- a/src/user/views/adsManager/views/advanced/components/campaign/fields/BudgetField.tsx
+++ b/src/user/views/adsManager/views/advanced/components/campaign/fields/BudgetField.tsx
@@ -1,4 +1,4 @@
-import { Box, Divider, InputAdornment, Stack, Typography } from "@mui/material";
+import { InputAdornment, Stack, Typography } from "@mui/material";
import { FormikRadioControl, FormikTextField } from "form/FormikHelpers";
import React, { useEffect, useState } from "react";
import { useFormikContext } from "formik";
@@ -6,7 +6,6 @@ import { CampaignForm } from "../../../../../types";
import { differenceInHours } from "date-fns";
import { MIN_PER_CAMPAIGN, MIN_PER_DAY } from "validation/CampaignSchema";
import { useAdvertiser } from "auth/hooks/queries/useAdvertiser";
-import { PaymentType } from "graphql/types";
import _ from "lodash";
import { CardContainer } from "components/Card/CardContainer";
@@ -62,7 +61,11 @@ export function BudgetField({ isEdit }: Props) {
: undefined
}
error={!!errors.budget || !!errors.dailyBudget}
- disabled={isEdit && !advertiser.selfServiceSetPrice}
+ disabled={
+ isEdit &&
+ !advertiser.selfServiceSetPrice &&
+ values.state !== "draft"
+ }
/>
{!advertiser.selfServiceSetPrice ? (
@@ -85,7 +88,7 @@ export function BudgetField({ isEdit }: Props) {
$
),
}}
- disabled={isEdit}
+ disabled={isEdit && values.state !== "draft"}
/>
)}
-
-
-
- Payment Method
-
-
- Prepayment of the campaign budget is required before your campaign
- can begin.{" "}
- {values.paymentType !== PaymentType.Stripe
- ? "We will contact you to arrange payment after you submit your campaign for approval."
- : ""}
-
-
-
);
diff --git a/src/user/views/adsManager/views/advanced/components/campaign/fields/PaymentMethodField.tsx b/src/user/views/adsManager/views/advanced/components/campaign/fields/PaymentMethodField.tsx
new file mode 100644
index 00000000..d3c8252d
--- /dev/null
+++ b/src/user/views/adsManager/views/advanced/components/campaign/fields/PaymentMethodField.tsx
@@ -0,0 +1,40 @@
+import { Stack, Typography } from "@mui/material";
+import { FormikRadioControl } from "form/FormikHelpers";
+import { PaymentType } from "graphql/types";
+import React from "react";
+import { useFormikContext } from "formik";
+import { CampaignForm } from "user/views/adsManager/types";
+import { useAdvertiser } from "auth/hooks/queries/useAdvertiser";
+import { CardContainer } from "components/Card/CardContainer";
+
+interface Props {
+ isEdit: boolean;
+}
+
+export function PaymentMethodField({ isEdit }: Props) {
+ const { values } = useFormikContext();
+ const { advertiser } = useAdvertiser();
+
+ if (advertiser.selfServiceSetPrice) {
+ return null;
+ }
+
+ return (
+
+
+
+ Prepayment of the campaign budget is required before your campaign can
+ begin.
+
+
+
+
+ );
+}
diff --git a/src/user/views/adsManager/views/advanced/components/completionForm/CompletionForm.tsx b/src/user/views/adsManager/views/advanced/components/completionForm/CompletionForm.tsx
index 145c2e86..549d5cf8 100644
--- a/src/user/views/adsManager/views/advanced/components/completionForm/CompletionForm.tsx
+++ b/src/user/views/adsManager/views/advanced/components/completionForm/CompletionForm.tsx
@@ -1,8 +1,7 @@
import React, { useState } from "react";
import { useHistory, useParams } from "react-router-dom";
-import { Box, Card, Container, Stack, Typography } from "@mui/material";
-import present from "../../../../../../../../present.png";
+import { Card, Container, Stack, Typography } from "@mui/material";
import { LoadingButton } from "@mui/lab";
import { useValidatePaymentSession } from "checkout/hooks/useValidatePaymentSession";
@@ -25,21 +24,17 @@ export function CompletionForm() {
});
return (
-
+
-
-
-
-
- Congratulations!
+ Congratulations! 🎉
{params.mode === "edit" && (
diff --git a/src/user/views/adsManager/views/advanced/components/form/EditCampaign.tsx b/src/user/views/adsManager/views/advanced/components/form/EditCampaign.tsx
index 217cad1f..017e84db 100644
--- a/src/user/views/adsManager/views/advanced/components/form/EditCampaign.tsx
+++ b/src/user/views/adsManager/views/advanced/components/form/EditCampaign.tsx
@@ -12,7 +12,6 @@ import { useHistory, useParams } from "react-router-dom";
import { BaseForm } from "./components/BaseForm";
import { useAdvertiser } from "auth/hooks/queries/useAdvertiser";
import { useCreatePaymentSession } from "checkout/hooks/useCreatePaymentSession";
-import { PaymentType } from "graphql/types";
import { ErrorDetail } from "components/Error/ErrorDetail";
interface Params {
@@ -36,11 +35,7 @@ export function EditCampaign() {
const [mutation] = useUpdateCampaignMutation({
onCompleted(data) {
- const campaign = initialData?.campaign;
- if (
- campaign?.stripePaymentId ||
- campaign?.paymentType !== PaymentType.Stripe
- ) {
+ if (initialData?.campaign?.hasPaymentIntent) {
history.push(
`/user/main/complete/edit?referenceId=${data.updateCampaign.id}`,
);
@@ -67,7 +62,6 @@ export function EditCampaign() {
}
const initialValues = editCampaignValues(initialData.campaign, advertiser.id);
-
return (
,
+ component: ,
},
{
label: "Ads",
diff --git a/src/user/views/adsManager/views/advanced/components/form/components/PaymentButton.tsx b/src/user/views/adsManager/views/advanced/components/form/components/PaymentButton.tsx
index 9df9272b..ae64a0fc 100644
--- a/src/user/views/adsManager/views/advanced/components/form/components/PaymentButton.tsx
+++ b/src/user/views/adsManager/views/advanced/components/form/components/PaymentButton.tsx
@@ -6,15 +6,13 @@ import { PaymentType } from "graphql/types";
export function PaymentButton(props: { isEdit: boolean }) {
const { values } = useFormikContext();
- const hasPaymentIntent =
- values.paymentType !== PaymentType.Stripe || values.stripePaymentId;
const paymentText = "Make payment & submit for approval";
return (