Skip to content

Instantly share code, notes, and snippets.

@sibelius
Created October 20, 2023 01:07
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save sibelius/bb95610d888f294b7ab0d3064f773e13 to your computer and use it in GitHub Desktop.
Save sibelius/bb95610d888f294b7ab0d3064f773e13 to your computer and use it in GitHub Desktop.
useOpenpix
import { useEffect, useState } from 'react';
const value = 100;
const PixDynamic = () => {
const [giftbackValue, setGiftbackValue] = useState(null);
const [giftbackHash, setGiftbackHash] = useState(null);
const [shopperId, setShopperId] = useState(null);
const [modal, setModal] = useState<boolean>(true);
// generate a new transactionID on mount
const [correlationID, setCorrelationID] = useState(() =>
getDefaultTransactionId(),
);
const onClick = () => {
const payload = {
correlationID,
value: value,
modal,
shopperId,
giftbackValue,
giftbackHash,
comment: 'good',
customer: { name: 'Dan', taxID: '31324227036' },
expiresIn: 35 * 60,
// enable auto close on success
// closeOnSuccess: true,
};
window.$openpix.push(['pix', payload]);
};
const isOpenPixLoaded = !!window.$openpix?.addEventListener;
useEffect(() => {
if (isOpenPixLoaded) {
const logEvents = (e) => {
// eslint-disable-next-line
console.log('logEvents: ', e);
if (e.type === EventType.PAYMENT_STATUS) {
if (e.data.status === 'COMPLETED') {
setCorrelationID(getDefaultTransactionId());
// wait 2 seconds to close modal
// setTimeout(() => {
// window.$openpix.push([
// 'close',
// ]);
// }, 2000);
}
}
};
const giftbackApplyEvent = (e) => {
// eslint-disable-next-line
console.log('logEvents: ', e);
if (e.type === EventType.GIFTBACK_APPLY) {
const { shopper, giftbackValue, giftbackHash } = e.data;
if (giftbackValue) {
setGiftbackValue(giftbackValue);
}
if (shopper?.id) {
setShopperId(shopper.id);
}
if (giftbackHash) {
setGiftbackHash(giftbackHash);
}
}
};
const unsubscribe = window.$openpix.addEventListener(logEvents);
const unsubscribeGiftback =
window.$openpix.addEventListener(giftbackApplyEvent);
return () => {
unsubscribe && unsubscribe();
unsubscribeGiftback && unsubscribeGiftback();
};
}
}, [isOpenPixLoaded]);
return (
<BoxFlex sx={{ flexDirection: 'column', gap: 16 }}>
<BoxFlex sx={{ flexDirection: 'column' }}>
<h2>Dynamic Pix React</h2>
<form
onChange={(e) => {
const value = e.target.value;
setModal(value === 'modal');
}}
>
<BoxFlex sx={{ justifyContent: 'space-between' }}>
<label htmlFor='renderType'>
<input
type='radio'
id='html'
name='type'
value='modal'
defaultChecked
/>
Modal
</label>
<label htmlFor='renderType'>
<input type='radio' id='html' name='type' value='div' />
Div
</label>
</BoxFlex>
</form>
<Button mt='10px' onClick={onClick}>
Pay with Pix
</Button>
</BoxFlex>
<div id='openpix'></div>
</BoxFlex>
);
};
export default PixDynamic;
import { useEffect } from 'react';
import { useScript } from './useScript';
import config from './config';
export type IOpenPixApi = {
generateStatic: (options: any) => any;
status: () => void;
addEventListener: () => void;
};
declare global {
interface Window {
$openpix: unknown[] & IOpenPixApi;
}
}
export const useOpenPix = () => {
useEffect(() => {
window.$openpix = [];
window.$openpix.push(['config', { appID: config.OPEN_PIX_APP_ID }]);
}, []);
const scriptURL = config.OPEN_PIX_URL;
// eslint-disable-next-line
const [loaded, error] = useScript({ src: scriptURL });
useEffect(() => {
if (!error) {
return;
}
// eslint-disable-next-line
console.log('OpenPix not loaded');
}, [error]);
};
import { useState, useEffect } from 'react';
export interface ScriptProps {
src: HTMLScriptElement['src'] | null;
checkForExisting?: boolean;
[key: string]: any;
}
type ErrorState = ErrorEvent | null;
type ScriptStatus = {
loading: boolean;
error: ErrorState;
scriptEl: HTMLScriptElement;
};
type ScriptStatusMap = {
[key: string]: ScriptStatus;
};
// Previously loading/loaded scripts and their current status
export const scripts: ScriptStatusMap = {};
// Check for existing <script> tags with this src. If so, update scripts[src]
// and return the new status; otherwise, return undefined.
const checkExisting = (src: string): ScriptStatus | undefined => {
const existing: HTMLScriptElement | null = document.querySelector(
`script[src="${src}"]`,
);
if (existing) {
// Assume existing <script> tag is already loaded,
// and cache that data for future use.
return (scripts[src] = {
loading: false,
error: null,
scriptEl: existing,
});
}
return undefined;
};
export const useScript = ({
src,
checkForExisting = true,
...attributes
}: ScriptProps): [boolean, ErrorState] => {
// Check whether some instance of this hook considered this src.
let status: ScriptStatus | undefined = src ? scripts[src] : undefined;
// If requested, check for existing <script> tags with this src
// (unless we've already loaded the script ourselves).
if (!status && checkForExisting && src && isBrowser) {
status = checkExisting(src);
}
const [loading, setLoading] = useState<boolean>(
status ? status.loading : Boolean(src),
);
const [error, setError] = useState<ErrorState>(status ? status.error : null);
useEffect(() => {
// Nothing to do on server, or if no src specified, or
// if loading has already resolved to "loaded" or "error" state.
if (!isBrowser || !src || !loading || error) return;
// Check again for existing <script> tags with this src
// in case it's changed since mount.
// eslint-disable-next-line react-hooks/exhaustive-deps
status = scripts[src];
if (!status && checkForExisting) {
status = checkExisting(src);
}
// Determine or create <script> element to listen to.
let scriptEl: HTMLScriptElement;
if (status) {
scriptEl = status.scriptEl;
} else {
scriptEl = document.createElement('script');
scriptEl.src = src;
Object.keys(attributes).forEach((key) => {
if (scriptEl[key] === undefined) {
scriptEl.setAttribute(key, attributes[key]);
} else {
scriptEl[key] = attributes[key];
}
});
status = scripts[src] = {
loading: true,
error: null,
scriptEl: scriptEl,
};
}
// `status` is now guaranteed to be defined: either the old status
// from a previous load, or a newly created one.
const handleLoad = () => {
if (status) status.loading = false;
setLoading(false);
};
const handleError = (error: ErrorEvent) => {
if (status) status.error = error;
setError(error);
};
scriptEl.addEventListener('load', handleLoad);
scriptEl.addEventListener('error', handleError);
document.body.appendChild(scriptEl);
return () => {
scriptEl.removeEventListener('load', handleLoad);
scriptEl.removeEventListener('error', handleError);
};
// we need to ignore the attributes as they're a new object per call, so we'd never skip an effect call
}, [src]);
return [loading, error];
};
const isBrowser =
typeof window !== 'undefined' && typeof window.document !== 'undefined';
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment