Skip to main content

Skrill JS Live Demo

Interactive example of the Skrill JS SDK integration.

Result
Loading...
Live Editor
function SkrillJsPaymentDemo() {
  const MERCHANT_ID = 0; // Change this to your actual Merchant ID (number) for production use
  const SESSION_ID = ''; // Change this to generated Session ID
  const [sdkStatus, setSdkStatus] = React.useState('idle');
  const [fieldsInitialized, setFieldsInitialized] = React.useState(false);
  const [paymentStatus, setPaymentStatus] = React.useState('');
  const [instance, setInstance] = React.useState(null);
  const [cardBrand, setCardBrand] = React.useState('');
  const [unsupportedCardBrand, setUnsupportedCardBrand] = React.useState('');
  const [fieldsValid, setFieldsValid] = React.useState(false);
  const [fieldEvents, setFieldEvents] = React.useState([]);
  const [isProcessingPayment, setIsProcessingPayment] = React.useState(false);
  const [fxConversionAgreement, setFxConversionAgreement] = React.useState(null);
  const [sdkLanguage, setSdkLanguage] = React.useState('EN');

  const sdkLoaded = React.useMemo(() => sdkStatus === 'loaded', [sdkStatus]);
  const hasUnsupportedCardBrand = React.useMemo(() => unsupportedCardBrand && unsupportedCardBrand === cardBrand, [unsupportedCardBrand, cardBrand]);

  // Load Skrill JS SDK
  React.useEffect(() => {
    if (!window.skrill) {
      const script = document.createElement('script');
      script.src = 'https://pay.skrill.com/skrill-js/skrill.min.js';
      script.onload = () => {
        setSdkStatus('loaded')
        setPaymentStatus('✅ Skrill JS SDK loaded successfully');
      };
      script.onerror = () => {
        setSdkStatus('failed')
        setPaymentStatus('❌ Failed to load Skrill JS SDK');
      };

      setSdkStatus('loading')
      document.head.appendChild(script);
    } else if (window.skrill) {
      setSdkStatus('loaded')
      setPaymentStatus('✅ Skrill JS SDK already loaded');
    }
  }, []);

  // Handle language changes
  React.useEffect(() => {
    if (sdkLanguage && instance) {
      // Note: setLanguage() only changes the SDK's internal language settings (e.g. VISA consent text).
      // It does not change the parent application's language.
      instance.setLanguage(sdkLanguage).then(() => setPaymentStatus(`🌍 SDK Language changed to: ${sdkLanguage}`))
    }
  }, [sdkLanguage, instance]);

  const initializeFields = async () => {
    try {
      setPaymentStatus('🔄 Initializing payment fields...');
      
      // Initialize Skrill JS fields
      const skrillJsInstance = await window.skrill.fields.setup(
        parseInt(MERCHANT_ID),
        SESSION_ID,
        {
          fields: {
            cardNumber: {
              selector: '#card-number-field',
              placeholder: '1234 5678 9012 3456'
            },
            cvv: {
              selector: '#cvv-field',
              placeholder: '123'
            },
            expiryDate: {
              selector: '#expiry-field',
              placeholder: 'MM/YY'
            },
            cardHolder: {
              selector: '#cardholder-field',
              placeholder: 'John Doe'
            },
            email: {
              selector: '#email-field',
              placeholder: 'john.doe@example.com'
            },
            consent: {
              selector: '#consent-field'
            }
          },
          style: {
            input: {
              'font-family': 'Arial, sans-serif',
              'font-weight': 'normal',
              'font-size': '16px',
              color: '#333333',
              padding: '0 12px'
            },
            '.valid': {
              color: '#157a38'
            },
            '.invalid': {
              color: '#B0460C'
            }
          },
          initializationTimeout: 20000
        }
      );

      skrillJsInstance.cardBrandRecognition((instance, event) => {
        setCardBrand(instance.getCardBrand());
      });

      skrillJsInstance.unsupportedCardBrand((instance, event) => {
        setUnsupportedCardBrand(event.data.cardBrand);
      });

      const setupFieldsEventListeners = (skrillJsInstance) => {
        const availableFields = ['cardNumber', 'cvv', 'expiryDate', 'cardHolder'];
        
        // Check if email field is available in the skrillJsInstance before subscribing to events
        if (skrillJsInstance.fields.email) {
          availableFields.push('email');
        }
        
        // Check if consent field is available in the skrillJsInstance before subscribing to events
        if (skrillJsInstance.fields.consent) {
          availableFields.push('consent');
        }

        const handleFieldEvent = (instance, event, error) => {
          setFieldsValid(instance.areAllFieldsValid());
          
          const eventLog = {
            timestamp: new Date().toLocaleTimeString(),
            event: event,
            error: error || null
          };
          
          setFieldEvents(prev => [eventLog, ...prev.slice(0, 19)]);
        };

        skrillJsInstance.fields(availableFields.join(' '))
          .on('focus blur valid invalid fieldValueChange', handleFieldEvent);
      };

      setupFieldsEventListeners(skrillJsInstance);
      setInstance(skrillJsInstance);
      setFieldsInitialized(true);
      setPaymentStatus({
        type: 'success',
        message: '✅ Payment fields initialized successfully',
        response: skrillJsInstance
      });
      
    } catch (error) {
      setPaymentStatus({
        type: 'error',
        message: `❌ Failed to initialize: ${error.message || error}`,
        response: error
      });
    }
  };

  const processPayment = async () => {
    if (!instance.areAllFieldsValid()) {
      setPaymentStatus('❌ Please complete all required fields');
      return;
    }

    try {
      setIsProcessingPayment(true);
      setPaymentStatus('🔄 Processing payment...');

      const response = await instance.pay( { fxConversionAccepted: !!fxConversionAgreement?.accepted } );

      switch (response.status) {
        case 'PROCESSED':
          setPaymentStatus({ type: 'success', message: '✅ Payment successful!', response });
          setFxConversionAgreement(null);
          break;
        case 'FAILED':
          setPaymentStatus({ type: 'error', message: '❌ Payment failed', response });
          setFxConversionAgreement(null);
          break;
        case 'FX_CONVERSION_NOTIFICATION':
          setFxConversionAgreement({
            processingAmount: response.processingAmount,
            processingCurrency: response.processingCurrency,
            accepted: false
          });
          setPaymentStatus({ type: 'info', message: '💱 FX conversion agreement required', response });
          break;
        default:
          setPaymentStatus({ type: 'info', message: `ℹ️ Payment status: ${response.status}`, response });
      }
    } catch (error) {
      setPaymentStatus({ 
        type: 'error', 
        message: `❌ Payment error: ${error.displayMessage || error.message || error}`,
        response: error
      });
      setFxConversionAgreement(null);
    } finally {
      setIsProcessingPayment(false);
    }
  };

  const focusField = (fieldName) => {
    instance.focusField(fieldName);
    setPaymentStatus(`🎯 Focus manually changed to ${fieldName}`);
  };

  const resetFields = () => {
    instance.resetCardDetails();
    setPaymentStatus('🔄 Card details reset');
    setFxConversionAgreement(null);
  };

  return (
    <div style={{ 
      maxWidth: '600px', 
      margin: '0 auto', 
      padding: '20px'
    }}>
      <h2>Pay with card</h2>

      <SdkLoadingSpinner sdkStatus={sdkStatus} />
      <div>
        {!fieldsInitialized && (
          <>
            <InitializePaymentFieldsButton 
              onClick={initializeFields}
              disabled={!sdkLoaded || !SESSION_ID || !MERCHANT_ID}
            />

            {sdkLoaded && (!MERCHANT_ID || !SESSION_ID) && (
                <InfoBox>
                  Make sure to set valid MERCHANT_ID and SESSION_ID values below before initializing.
                </InfoBox>
            )}
          </>
        )}

        {fieldsInitialized && (
          <div style={{ display: 'flex', alignItems: 'flex-end' }}>
            <ResetCardDetailsButton onClick={resetFields} />
            <LanguageSelector sdkLanguage={sdkLanguage} setSdkLanguage={setSdkLanguage} />
          </div>
        )}
      </div>

      {/* Payment Fields Section */}
        <div style={{
          backgroundColor: '#ffffff',
          padding: '20px',
          borderRadius: '8px',
          marginBottom: '20px',
          border: '2px solid #e1e5e9'
        }}
        // IMPORTANT: Use 'hidden' instead of conditional rendering to keep DOM placeholders available.
        // Skrill JS SDK requires all field containers to exist in the DOM during the whole process.
        // Conditional rendering (&&) would remove elements and cause initialization failures.
        hidden={!fieldsInitialized}
        >
          <h3>Card Details</h3>
          
          {/* Card Number - Full width */}
          <div style={{ marginBottom: '15px' }}>
            <label style={{ display: 'block', marginBottom: '8px', fontWeight: 'bold', color: '#495057' }}
              onClick={() => focusField('cardNumber')}
            >
              Card Number:
            </label>
            <div id="card-number-field" style={{ 
              height: '44px', 
              border: '1px solid #ccc', 
              borderRadius: '4px',
              backgroundColor: '#fff'
            }}></div>
          </div>

          {/* Expiry Date and CVV - Same row */}
          <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '15px', marginBottom: '15px' }}>
            <div>
              <label style={{ display: 'block', marginBottom: '8px', fontWeight: 'bold', color: '#495057' }}
                onClick={() => focusField('expiryDate')}
              >
                Expiry Date:
              </label>
              <div id="expiry-field" style={{ 
                height: '44px', 
                border: '1px solid #ccc', 
                borderRadius: '4px',
                backgroundColor: '#fff'
              }}></div>
            </div>
            <div>
              <label style={{ display: 'block', marginBottom: '8px', fontWeight: 'bold', color: '#495057' }}
                onClick={() => focusField('cvv')}
              >
                CVV:
              </label>
              <div id="cvv-field" style={{ 
                height: '44px', 
                border: '1px solid #ccc', 
                borderRadius: '4px',
                backgroundColor: '#fff'
              }}></div>
            </div>
          </div>

          {/* Cardholder Name - Full width */}
          <div style={{ marginBottom: '15px' }}>
            <label style={{ display: 'block', marginBottom: '8px', fontWeight: 'bold', color: '#495057' }}
            onClick={() => focusField('cardHolder')}
            >
              Cardholder Name:
            </label>
            <div id="cardholder-field" style={{ 
              height: '44px', 
              border: '1px solid #ccc', 
              borderRadius: '4px',
              backgroundColor: '#fff'
            }}></div>
          </div>

          {/* Email - Full width */}
          <div style={{ marginBottom: '15px' }} hidden={!instance?.fields.email}>
            <label style={{ display: 'block', marginBottom: '8px', fontWeight: 'bold', color: '#495057' }}
            onClick={() => focusField('email')}
            >
              Email:
            </label>
            <div id="email-field" style={{ 
              height: '44px', 
              border: '1px solid #ccc', 
              borderRadius: '4px',
              backgroundColor: '#fff'
            }}></div>
          </div>

          {/* Consent - Full width, no label, no borders */}
          <div style={{ marginBottom: '20px' }} hidden={!instance?.fields.consent}>
            <div id="consent-field" style={{ 
              height: '44px'
            }}></div>
          </div>

          <CardBrandInfo cardBrand={cardBrand} />

          {hasUnsupportedCardBrand && (
            <WarningBox>
              The detected card brand ({cardBrand}) is not supported. Please use a supported card type.
            </WarningBox>
          )}

          <FxConversionAgreement 
            fxConversionAgreement={fxConversionAgreement} 
            onChange={setFxConversionAgreement} 
          />

          <PayButton 
            onClick={processPayment} 
            disabled={!fieldsValid || isProcessingPayment} 
          />
        </div>

      <PaymentStatus paymentStatus={paymentStatus} />

      <FieldEvents fieldEvents={fieldEvents} fieldsInitialized={fieldsInitialized} />
    </div>
  );
}

Getting Started

To integrate Skrill JS SDK in your application:

  1. Include the SDK: Load the Skrill JS script in your HTML
  2. Configure Fields: Set up hosted fields with your merchant ID and session ID
  3. Handle Events: Listen for validation, card brand detection, and payment events
  4. Process Payments: Use the pay() method to securely process payments

The demo above shows a complete integration where different scenarios can be tried and experimented with.