Search code examples
pythonpython-3.xdjangodjango-rest-frameworkadyen

CashApp Payment Method Not Rendering in Django with Adyen Integration


I'm trying to integrate Adyen's payment methods (including CashApp) into my Django web application, but the CashApp payment method isn't rendering on the front end. Instead, I receive the following error:

ERROR Error during initialization ERROR: Error during initialization
at e.<anonymous> (https://checkoutshopper-test.adyen.com/checkoutshopper/sdk/5.68.0/adyen.js:1:460538)
at P (https://checkoutshopper-test.adyen.com/checkoutshopper/sdk/5.68.0/adyen.js:1:41524)
at Generator.<anonymous> (https://checkoutshopper-test.adyen.com/checkoutshopper/sdk/5.68.0/adyen.js:1:42844)

This same code is rendering the card payment gateway but not working on the cashapp.

My html Code: Where is used adyen sdk=5.68.0 version. Version greater than 5.69.0 gives different error that AdyenCheckout is not defined I don't know how to solve that too.


<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<meta name="vtokvaldat" content="{{ csrf_token }}">


<!-- Adyen css from TEST environment (change to live for production)-->
<link rel="stylesheet"
        href="https://checkoutshopper-test.adyen.com/checkoutshopper/sdk/5.68.0/adyen.css"
        integrity="sha384-gpOE6R0K50VgXe6u/pyjzkKl4Kr8hXu93KUCTmC4LqbO9mpoGUYsrmeVLcp2eejn"
        crossorigin="anonymous">

<div id="payment-page">
    <div class="container">
        <div class="payment-container">

            <div id="component" class="payment">
                <!-- Component will be rendered here -->
            </div>

        </div>
    </div>
    {% csrf_token %}
</div>

<!-- Adyen JS from TEST environment (change to live for production)-->
<script src="https://checkoutshopper-test.adyen.com/checkoutshopper/sdk/5.68.0/adyen.js"
        integrity="sha384-U9GX6Oa3W024049K86PYG36/jHjkvUqsRd8Y9cF1CmB92sm4tnjxDXF/tkdcsk6k"
        crossorigin="anonymous"></script>


{{ client_key|json_script:"client-key" }}
{{ method|json_script:"integration-type" }}
<script>
    const clientKey = JSON.parse(document.getElementById('client-key').innerHTML);
    const type = JSON.parse(document.getElementById('integration-type').innerHTML);

    // Used to finalize a checkout call in case of redirect
    const urlParams = new URLSearchParams(window.location.search);
    const sessionId = urlParams.get('sessionId'); // Unique identifier for the payment session
    const redirectResult = urlParams.get('redirectResult');


    // Start the Checkout workflow
    async function startCheckout() {
        try {
            // Init Sessions
            const checkoutSessionResponse = await callServer({% url 'sessions' %});
            // Create AdyenCheckout using Sessions response
            const checkout = await createAdyenCheckout(checkoutSessionResponse)
            // Create an instance of Drop-in and mount it to the container you created.
            const dropinComponent = checkout.create(type).mount("#component");  // pass DIV id where component must be rendered
        } catch (error) {
            console.error(error);
            alert("Error occurred. Look at console for details");
        }
    }

    // Some payment methods use redirects. This is where we finalize the operation
    async function finalizeCheckout() {
        try {
            // Create AdyenCheckout re-using existing Session
            const checkout = await createAdyenCheckout({id: sessionId});

            // Submit the extracted redirectResult (to trigger onPaymentCompleted(result, component) handler)
            checkout.submitDetails({details: {redirectResult}});
        } catch (error) {
            console.error(error);
            alert("Error occurred. Look at console for details");
        }
    }

    function getCSRFToken() {
        const token = document.querySelector('meta[name="vtokvaldat"]').getAttribute('content');
        return token;
    }

    async function createAdyenCheckout(session) {

        const configuration = {
            clientKey,
            locale: "en-US",
            environment: "test",  // change to live for production
            showPayButton: true,
            session: session,
            paymentMethodsConfiguration: {
                ideal: {
                    showImage: true
                },
                card: {
                    hasHolderName: true,
                    holderNameRequired: true,
                    name: "Credit or debit card",
                    countryCode: "US"
                },
                paypal: {
                    environment: "test",
                    countryCode: "US"   // Only needed for test. This will be automatically retrieved when you are in production.
                },
                cashapp: {
                    environment: "test",
                    countryCode: "US",
                    referenceId: "5465456435156",
                    showsStorePaymentMethodField: true,
                    storePaymentMethod: true
                }
            },
            onPaymentCompleted: (result, component) => {
                console.info(result, component);
                handleServerResponse(result, component);
            },
            onError: (error, component) => {
                console.error(error.name, error.message, error.stack, component);
            }
        };

        return new AdyenCheckout(configuration);
    }


    // Calls your server endpoints
    async function callServer(url, data) {
        const res = await fetch(url, {
            method: "POST",
            body: data ? JSON.stringify(data) : "",
            headers: {
                "Content-Type": "application/json",
                "X-CSRFToken": getCSRFToken()
            }
        });
        return await res.json();
    }

    // Handles responses sent from your server to the client
    function handleServerResponse(res, component) {
        if (res.action) {
            component.handleAction(res.action);
        } else {
            switch (res.resultCode) {
                case "Authorised":
                    window.location.href = "{% url 'success' %}";
                    break;
                case "Pending":
                case "Received":
                    window.location.href = "/pending";
                    break;
                case "Refused":
                    window.location.href = "{% url 'failed' %}";
                    break;
                default:
                    window.location.href = "{% url 'error' %}";
                    break;
            }
        }
    }

    if (!sessionId) {
        startCheckout();
    }
    else {
        // existing session: complete Checkout
        finalizeCheckout();
    }
</script>

django view.py file:

import json
import uuid
from django.conf import settings
from django.http import JsonResponse
from django.shortcuts import render
import Adyen

def adyen_sessions(request):
    # payments = ["card", "cashapp", "paypal"]
    session_response = {
        'client_key': settings.ADYEN_CLIENT_KEY,
        'method': 'cashapp'
    }
    return render(request, 'component.html', context=session_response)

def sessions(request):
    if request.method == "POST":
        adyen = Adyen.Adyen()
        adyen.payment.client.xapikey = settings.ADYEN_API_KEY
        adyen.payment.client.platform = settings.ADYEN_ENVIRONMENT
        adyen.payment.client.merchant_account = settings.ADYEN_MERCHANT_ACCOUNT

        request_data = {
            'amount': {
                "value": 1900,  # amount in minor units
                "currency": "USD"
            },
            'reference': f"Reference {uuid.uuid4()}",
            'returnUrl': f"{request.build_absolute_uri('/redirect?shopperOrder=myRef')}",
            'countryCode': "US",
            'channel': 'Web',
            "recurringProcessingModel": "CardOnFile",
            "storePaymentMethodMode": "askForConsent",
            'lineItems': [
                {
                    "quantity": 1,
                    "amountIncludingTax": 5000,
                    "description": "Sunglasses"
                },
                {
                    "quantity": 1,
                    "amountIncludingTax": 5000,
                    "description": "Headphones"
                }
            ],
            'merchantAccount': settings.ADYEN_MERCHANT_ACCOUNT,
        }
        result = adyen.checkout.payments_api.sessions(request_data)
        data = json.loads(result.raw_response)
        print("/sessions response:\n" + data.__str__())
        return JsonResponse(data, status=200, safe=False)

def redirect_view(request):
    return render(request, 'home.html')

def success(request):
    return render(request, 'checkout-success.html')

def failed(request):
    return render(request, 'checkout-failed.html')

def error(request):
    return render(request, 'error.html')

django urls.py:

from django.urls import path
from . import views

urlpatterns = [
    path('', views.adyen_sessions, name='adyen_sessions'),
    path('redirect/', views.redirect_view, name='redirect_view'),
    path('sessions/', views.sessions, name='sessions'),
    path('success/', views.success, name='success'),
    path('failed/', views.failed, name='failed'),
    path('error/', views.error, name='error'),
]

ERROR Image: Error Image


Solution

  • According to the Adyen Documentation for CashApp you have to include the following two parameters in your request:

    • "storePaymentMethodMode": "askForConsent"
    • "recurringProcessingModel": "CardOnFile"

    I'd also ensure that startCheckout(..) is called on the page. I don't see the startCheckout(..) being called in the inserted snippet.

    example of cash app rendering on Drop-in

    Here's my version of the code, I've started with the Python-integration-example on Github and modified it slightly:

    // sessions.py (backend)
    
    def adyen_sessions(host_url):
        adyen = Adyen.Adyen()
        adyen.payment.client.xapikey = get_adyen_api_key()
        adyen.payment.client.platform = "test"
        adyen.payment.client.merchant_account = get_adyen_merchant_account()
    
        request_data = {
            'amount': {
                'value': 1900, 
                'currency': 'USD'
            },
            'reference': f"Reference {uuid.uuid4()}",
            'returnUrl': f"{host_url}/redirect?shopperOrder=myRef", # Modify this so it will return the user to your specific page when redirected
            'countryCode': 'US',
            'channel': 'Web',
            'recurringProcessingModel': 'CardOnFile',
            'storePaymentMethodMode': 'askForConsent',
            
            'lineItems': [
                {
                    'quantity': 1,
                    'amountIncludingTax': 5000,
                    'description': 'Sunglasses'
                },
                {
                    'quantity': 1,
                    'amountIncludingTax': 5000,
                    'description': 'Headphones'
                }
            ],
            'merchantAccount': get_adyen_merchant_account(),
        }
        result = adyen.checkout.payments_api.sessions(request_data)
        formatted_response = json.dumps((json.loads(result.raw_response)))
        print("/sessions response:\n" + formatted_response)
        return formatted_response
    
    // In config.py (added 'cashapp' for components)
    def get_supported_integration():
        return ['dropin', 'card', 'ideal', 'klarna', 'directEbanking', 'alipay', 'boletobancario',
                'sepadirectdebit', 'dotpay', 'giropay', 'ach', 'paypal', 'applepay',
                'klarna_paynow', 'klarna', 'klarna_account', 'cashapp']
    
    
    // Frontend
    const clientKey = JSON.parse(document.getElementById('client-key').innerHTML);
    const type = JSON.parse(document.getElementById('integration-type').innerHTML);
    
    // Used to finalize a checkout call in case of redirect
    const urlParams = new URLSearchParams(window.location.search);
    const sessionId = urlParams.get('sessionId'); // Unique identifier for the payment session
    const redirectResult = urlParams.get('redirectResult');
    
    
    // Start the Checkout workflow
    async function startCheckout() {
        try {
            // Init Sessions
            const checkoutSessionResponse = await callServer("/api/sessions?type=" + type);
    
            // Create AdyenCheckout using Sessions response
            const checkout = await createAdyenCheckout(checkoutSessionResponse)
    
            // Create an instance of Drop-in and mount it to the container you created.
            const dropinComponent = checkout.create(type).mount("#component");
        } catch (error) {
            console.error(error);
            alert("Error occurred. Look at console for details");
        }
    }
    
    // Some payment methods use redirects. This is where we finalize the operation
    async function finalizeCheckout() {
        try {
            // Create AdyenCheckout re-using existing Session
            const checkout = await createAdyenCheckout({id: sessionId});
    
            // Submit the extracted redirectResult (to trigger onPaymentCompleted(result, component) handler)
            checkout.submitDetails({details: {redirectResult}});
        } catch (error) {
            console.error(error);
            alert("Error occurred. Look at console for details");
        }
    }
    
    function getCSRFToken() {
        const token = "token";// document.querySelector('meta[name="vtokvaldat"]').getAttribute('content'); // Our example doesn't have this field, hence why I left this one out for now
        return token;
    }
    
    async function createAdyenCheckout(session) {
    
        const configuration = {
            clientKey,
            locale: "en-US",
            environment: "test",  // change to live for production
            showPayButton: true,
            session: session,
            paymentMethodsConfiguration: {
                ideal: {
                    showImage: true
                },
                card: {
                    hasHolderName: true,
                    holderNameRequired: true,
                    name: "Credit or debit card",
                    countryCode: "US"
                },
                paypal: {
                    environment: "test",
                    countryCode: "US"   // Only needed for test. This will be automatically retrieved when you are in production.
                },
                cashapp: {
                    environment: "test",
                    countryCode: "US",
                    referenceId: "5465456435156",
                    showsStorePaymentMethodField: true,
                    storePaymentMethod: true,
                    style: { button: { shape: 'semiround' } }
                }
            },
            onPaymentCompleted: (result, component) => {
                console.info(result, component);
                handleServerResponse(result, component);
            },
            onError: (error, component) => {
                console.error(error.name, error.message, error.stack, component);
            }
        };
    
        return new AdyenCheckout(configuration);
    }
    
    
    // Calls your server endpoints
    async function callServer(url, data) {
        const res = await fetch(url, {
            method: "POST",
            body: data ? JSON.stringify(data) : "",
            headers: {
                "Content-Type": "application/json",
                "X-CSRFToken": getCSRFToken()
            }
        });
        return await res.json();
    }
    
    // Handles responses sent from your server to the client
    function handleServerResponse(res, component) {
        if (res.action) {
            component.handleAction(res.action);
        } else {
            switch (res.resultCode) {
                case "Authorised":
                    window.location.href = "{% url 'success' %}";
                    break;
                case "Pending":
                case "Received":
                    window.location.href = "/pending";
                    break;
                case "Refused":
                    window.location.href = "{% url 'failed' %}";
                    break;
                default:
                    window.location.href = "{% url 'error' %}";
                    break;
            }
        }
    }
    
    if (!sessionId) {
        startCheckout();
    }
    else {
        // existing session: complete Checkout
        finalizeCheckout();
    }
    
    

    Hope this helped!