Loop Crypto Custom Integration
Prerequisites
Install the required dependencies:
npm install @getopenpay/openpay-js-react @loop-crypto/connect
Example
import {
ElementsForm,
FieldName,
SubmitMethods
} from '@getopenpay/openpay-js-react';
import { initLoopConnect, LoopConnectPayIn } from '@loop-crypto/connect';
import { useState, useEffect } from 'react';
function LoopCheckoutForm() {
const [error, setError] = useState<string | undefined>(undefined);
const [amount, setAmount] = useState<string | undefined>(undefined);
const [isLoopInitialized, setIsLoopInitialized] = useState(false);
const [createdCustomer, setCreatedCustomer] = useState(null);
const [createdPaymentMethod, setCreatedPaymentMethod] = useState(null);
const [isAuthorized, setIsAuthorized] = useState(false);
const onLoad = (totalAmountAtoms: number) => {
setAmount(`$${totalAmountAtoms / 100}`);
};
const onCheckoutError = (message: string) => {
setError(message);
};
const onCheckoutSuccess = (invoiceUrls: string[]) => {
console.log('Payment successful! Invoice URLs:', invoiceUrls);
};
return (
<ElementsForm
checkoutSecureToken={`checkout-secure-token-uuid`}
onChange={() => setError(undefined)}
onLoad={onLoad}
onValidationError={(message) => setError(message)}
onCheckoutSuccess={onCheckoutSuccess}
onCheckoutError={onCheckoutError}
>
{({ submitWith, loop }) => {
useEffect(() => {
if (loop?.isAvailable && loop.config) {
initLoopConnect({
jwtToken: loop.config.apiToken,
entityId: loop.config.entityId,
merchantId: loop.config.merchantId,
environment: loop.config.environment,
onInitialized: () => setIsLoopInitialized(true),
onInitFailed: (detail) => {
submitWith(SubmitMethods.loopCrypto, {
success: false,
failureDetails: detail,
});
},
});
}
}, [loop]);
if (!loop?.isAvailable) {
return <div>Loop Crypto is not available for this checkout</div>;
}
if (!isLoopInitialized) {
return <div>Loading Loop widget...</div>;
}
return (
<>
<div>
<input type="text" placeholder="Given name" data-opid={FieldName.FIRST_NAME} />
<input type="text" placeholder="Family name" data-opid={FieldName.LAST_NAME} />
<input type="email" placeholder="Email" data-opid={FieldName.EMAIL} />
<input type="text" placeholder="Country" data-opid={FieldName.COUNTRY} />
<input type="text" placeholder="ZIP" data-opid={FieldName.ZIP_CODE} />
</div>
<div>
<LoopConnectPayIn
paymentUsdAmount={0}
minimumAuthorizationUsdAmount={loop.widget?.minAuthorizationUsdAmount}
customerRefId={loop.widget?.customerRefId}
addToExistingAuthorization={true}
awaitConfirmation={true}
stateNotification="embedded"
onPayInCustomerCreated={(customer) => {
const minCustomer = {
customerId: customer.customerId,
customerRefId: customer.customerRefId,
};
const paymentMethod = customer.paymentMethods[0];
setCreatedCustomer(minCustomer);
setCreatedPaymentMethod(paymentMethod);
if (isAuthorized) {
submitWith(SubmitMethods.loopCrypto, {
success: true,
customer: minCustomer,
paymentMethod: paymentMethod,
});
}
}}
onPayInAuthorizationConfirmed={(detail) => {
setIsAuthorized(true);
if (createdCustomer && createdPaymentMethod) {
submitWith(SubmitMethods.loopCrypto, {
success: true,
customer: createdCustomer,
paymentMethod: createdPaymentMethod,
});
}
}}
onPayInStateChange={(detail) => {
console.log('Loop state:', detail.state, detail.message);
}}
onPayInFailed={(detail) => {
submitWith(SubmitMethods.loopCrypto, {
success: false,
failureDetails: detail,
});
}}
/>
</div>
{error && <small>{error}</small>}
</>
);
}}
</ElementsForm>
);
}
export default LoopCheckoutForm;
Key Components
LoopConnect
Please see the office docs of LoopConnect here: https://docs.loopcrypto.xyz/docs/loopconnect-library
LoopConfig
The LoopConfig
object provided by ElementsForm
contains:
interface LoopConfig {
isAvailable: boolean;
config?: {
apiToken: string;
entityId: string;
merchantId: string;
environment: string;
};
widget?: {
paymentUsdAmount: number;
suggestedAuthorizationUsdAmount: number;
minAuthorizationUsdAmount: number;
customerRefId: string;
allowCustomerToChooseAuth: boolean;
};
}
Submission Flow
The Loop integration follows a two-step confirmation flow:
-
Customer Creation: Widget creates a Loop customer and payment method
-
Authorization Confirmation: Blockchain confirms the authorization
Both callbacks (onPayInCustomerCreated
and onPayInAuthorizationConfirmed
) must fire before submitting. The order can vary, so track both states and submit when both are complete.
Best Practices
-
State Management: Track customer creation and authorization separately as they can happen in any order
-
Form Validation: Validate required form fields before enabling the widget
-
Loading States: Show appropriate loading indicators during initialization and submission