<div class="container">
  <div class="row">
    <div class="col-12">
      <div id="PaymentStatus"></div>
    </div>
    <div class="col-12">
      <div class="card" style="width: 18rem;">
        <div class="card-body">
          <h5 class="card-title">Pagar con Apple Pay</h5>
          <div id="ckoApplePay" class="apple-pay-button apple-pay-button-text-plain" lang=es>
        </div>
                    <p><small>Este ejemplo se ejecuta dentro de un iframe y Apple no lo permite. Para ver el comportamiento completo <a href="https://codepen.io/pen/debug/VwXBQyp">visite este enlace.</a></small></p>
          <p style="display:none" id="ckoApplePayError">Su navegador o equipo no soporta Apple Pay.</p>
      </div>            
      </div>
    </div>
  </div>
</div>
.apple-pay-button {
  width: 250px;
  height: 40px;
  display: inline-block;
  -webkit-appearance: -apple-pay-button;
  cursor: pointer;
}

.apple-pay-button-with-text>* {
  display: none;
}

.apple-pay-button-black-with-text {
  -apple-pay-button-style: black;
}

.apple-pay-button-white-with-text {
  -apple-pay-button-style: white;
}

.apple-pay-button-white-with-line-with-text {
  -apple-pay-button-style: white-outline;
}

.apple-pay-button-text-book {
  -apple-pay-button-type: book;
}

.apple-pay-button-text-buy {
  -apple-pay-button-type: buy;
}

.apple-pay-button-text-check-out {
  -apple-pay-button-type: check-out;
}

.apple-pay-button-text-donate {
  -apple-pay-button-type: donate;
}

/* For mobile devices */

@media only screen and (max-width: 600px) {
  .apple-pay-button {
    width: 90%;
    height: 50px;
  }
}
var applePayUiController = (function () {
  var DOMStrings = {
    appleButton: 'ckoApplePay',
    errorMessage: 'ckoApplePayError'
  }
  return {
    DOMStrings,
    displayApplePayButton: function () {
      document.getElementById(DOMStrings.appleButton).style.display = 'block'
    },
    hideApplePayButton: function () {
      document.getElementById(DOMStrings.appleButton).style.display = 'none'
    },
    displayErrorMessage: function () {
      document.getElementById(DOMStrings.errorMessage).style.display = 'block'
    }
  }
})()

var applePayController = (function (uiController) {
  // BEGIN SIPAY CUSTOMIZE
  var BACKEND_URL_VALIDATE_SESSION = 'https://enpr4mevu14qbjf.m.pipedream.net'
  var BACKEND_URL_PAY = 'https://enl6ugm0xqdbbf3.m.pipedream.net'
  // END SIPAY CUSTOMIZE

  var request_id = ''

  // High level configuration options.
  // BEGIN SIPAY CUSTOMIZE
  var config = {
    payments: {
      acceptedCardSchemes: ['amex', 'masterCard', 'maestro', 'visa', 'mada']
    },
    shop: {
      product_price: 10.0,
      shop_name: 'Tienda Demo',
      shop_localisation: {
        currencyCode: 'EUR',
        countryCode: 'ES'
      }
    },
    shipping: {
      ES_region: [
        {
          label: 'Free Shipping',
          amount: '0.00',
          detail: 'Arrives in 3-5 days',
          identifier: 'freeShipping'
        },
        {
          label: 'Express Shipping',
          amount: '5.00',
          detail: 'Arrives in 1-2 days',
          identifier: 'expressShipping'
        }
      ],
      WORLDWIDE_region: [
        {
          label: 'Worldwide Standard Shipping',
          amount: '10.00',
          detail: 'Arrives in 5-8 days',
          identifier: 'worldwideShipping'
        }
      ]
    }
  }
  // END SIPAY CUSTOMIZE
  /**
   * Checks if Apple Pay is possible in the current environment.
   * @return {boolean} Boolean to check if Apple Pay is possible
   */
  var _applePayAvailable = function () {
    return window.ApplePaySession && ApplePaySession.canMakePayments()
  }

  /**
   * Starts the Apple Pay session using a configuration
   */
  var _startApplePaySession = function (config) {
    var applePaySession = new ApplePaySession(6, config)
    _handleApplePayEvents(applePaySession)
    applePaySession.begin()
  }

  /**
   * This method cals your backend server with the Apple Pay validation URL.
   * On the backend, a POST request will be done to this URL with the Apple Pay certificates
   * and the outcome will be returned
   *
   * @param {string} appleUrl The Apple Pay validation URL generated by Apple
   * @param {function} callback Callback function used to return the server call outcome
   *
   * @return {object} The session payload
   *
   */
  var _validateApplePaySession = function (appleUrl, callback) {
    // I'm using AXIOS to do a POST request to the backend but any HTTP client can be used

    const message = {
      // BEGIN SIPAY CUSTOMIZE
        // 'url': appleUrl, <- LIVE
        'url': 'https://apple-pay-gateway-cert.apple.com/paymentservices/paymentSession', // <- SANDBOX
        'domain': window.location.host, // Web domain
        'title': 'Sale'
        // END SIPAY CUSTOMIZE
    }
    axios
      .post(
        BACKEND_URL_VALIDATE_SESSION,
        message,
        {
          headers: { 
            'Access-Control-Allow-Origin': '*',
            'Access-Control-Max-Age': '1000'
          }
        }
      )
      .then(function (response) {
        callback(response.data.payload)
        request_id = response.data.payload.request_id
      })
  }

  /**
   * This method returns the available payment methods for a certain region. You can add
   * your business logic here to determine the shipping methods you need.
   *
   * @param {string} 2 Letter ISO of the region
   *
   * @return {Array} An array of shipping methods
   *
   */
  var _getAvailableShippingMethods = function (region) {
    // return the shipping methods available based on region
    if (region === 'ES') {
      return { methods: config.shipping.ES_region }
    } else {
      return { methods: config.shipping.WORLDWIDE_region }
    }
  }

  var _calculateTotal = function (subtotal, shipping) {
    return (parseFloat(subtotal) + parseFloat(shipping)).toFixed(2)
  }

  // here we talk to our backend to send the Apple Pay Payload and return the transaction outcome
  var _performTransaction = function (details, callback) {
    // I'm using AXIOS to do a POST request to the backend but any HTTP client can be used
    axios
      .post(
        BACKEND_URL_PAY,
        {
          token: details.token,
          request_id: request_id,
          // customerEmail: details.shippingContact.emailAddress,
          // billingDetails: details.billingContact,
          // shippingDetails: details.shippingContact
        },
        {
          headers: { 'Access-Control-Allow-Origin': '*' }
        }
      )
      .then(function (response) {
        console.log(response);
        callback(response.data)
      })
  }

  /**
   * This is the main method of the script, since here we handle all the Apple Pay
   * events. Here you are able to populate your shipping methods, react to  shipping methods
   * changes, and many other interaction that the user has with the Apple Pay pup-up.
   *
   * @param {object} Apple Pay Session (the one generate on the button click)
   *
   */
  var _handleApplePayEvents = function (appleSession) {
    // This is the first event that Apple triggers. Here you need to validate the
    // Apple Pay Session from your Back-End
    appleSession.onvalidatemerchant = function (event) {
      _validateApplePaySession(event.validationURL, function (merchantSession) {
        appleSession.completeMerchantValidation(merchantSession)
      })
    }

    // This method is triggered before populating the shipping methods. This is the
    // perfect place inject your shipping methods
    appleSession.onshippingcontactselected = function (event) {
      // populate with the availbale shipping methods for the region (Apple will tell you the region).
      // while the full address will only be available to you after the user confirms tha payment
      var shipping = _getAvailableShippingMethods(
        config.shop.shop_localisation.countryCode
      )
      // Update total and line items based on the shipping methods
      var newTotal = {
        type: 'final',
        label: config.shop.shop_name,
        amount: _calculateTotal(
          config.shop.product_price,
          shipping.methods[0].amount
        )
      }
      var newLineItems = [
        {
          type: 'final',
          label: 'Subtotal',
          amount: config.shop.product_price
        },
        {
          type: 'final',
          label: shipping.methods[0].label,
          amount: shipping.methods[0].amount
        }
      ]
      appleSession.completeShippingContactSelection(
        ApplePaySession.STATUS_SUCCESS,
        shipping.methods,
        newTotal,
        newLineItems
      )
    }

    // This method is triggered when a user select one of the shipping options.
    // Here you generally want to keep track of the transaction amount
    appleSession.onshippingmethodselected = function (event) {
      var newTotal = {
        type: 'final',
        label: config.shop.shop_name,
        amount: _calculateTotal(
          config.shop.product_price,
          event.shippingMethod.amount
        )
      }
      var newLineItems = [
        {
          type: 'final',
          label: 'Subtotal',
          amount: config.shop.product_price
        },
        {
          type: 'final',
          label: event.shippingMethod.label,
          amount: event.shippingMethod.amount
        }
      ]
      appleSession.completeShippingMethodSelection(
        ApplePaySession.STATUS_SUCCESS,
        newTotal,
        newLineItems
      )
    }

    // This method is the most important method. It gets triggered after the user has
    // confirmed the transaction with the Touch ID or Face ID. Besides getting all the
    // details about the customer (email, address ...) you also get the Apple Pay payload
    // needed to perform a payment.
    appleSession.onpaymentauthorized = function (event) {
      _performTransaction(event.payment, function (outcome) {
        console.log('### BEGIN outcome ###')
        console.log(outcome);
        console.log('*** END outcome ***')
        const paymentStatusElement = document.getElementById('PaymentStatus');
        if (outcome.payload.code == 0) {
          appleSession.completePayment(ApplePaySession.STATUS_SUCCESS)
          // BEGIN SIPAY CUSTOMIZE
          paymentStatusElement.innerHTML = '<img src="images/ok.png" /><p><h1>¡PAGO REALIZADO CON ÉXITO!</h1></p>';
          uiController.hideApplePayButton()
          console.log('Authorization Succesful');
          // END SIPAY CUSTOMIZE
        } else {
          appleSession.completePayment(ApplePaySession.STATUS_FAILURE)
          // BEGIN SIPAY CUSTOMIZE
          paymentStatusElement.innerHTML = '<img src="images/ko.png" /><p><h1>¡PAGO FALLIDO!</h1></p>';
          uiController.hideApplePayButton()
          console.log('Authorization Fail');
          // END SIPAY CUSTOMIZE
        }
      })
    }
  }

  /**
   * Sets a onClick listen on the Apple Pay button. When clicked it will
   * begin the Apple Pay session with your configuration
   */
  var _setButtonClickListener = function () {
    document
      .getElementById(uiController.DOMStrings.appleButton)
      .addEventListener('click', function () {
        _startApplePaySession({
          currencyCode: config.shop.shop_localisation.currencyCode,
          countryCode: config.shop.shop_localisation.countryCode,
          merchantCapabilities: [
            'supports3DS',
            'supportsEMV',
            'supportsCredit',
            'supportsDebit'
          ],
          supportedNetworks: config.payments.acceptedCardSchemes,
          shippingType: 'shipping',
          requiredBillingContactFields: [
            // 'postalAddress',
            // 'name',
            // 'phone',
            // 'email'
          ],
          requiredShippingContactFields: [
            // 'postalAddress',
            // 'name',
            // 'phone',
            // 'email'
          ],
          applicationData: 'aG9sYSBwcm9iYW5kbyBjdXN0b20gZmllbGQ=',
          total: {
            label: config.shop.shop_name,
            amount: config.shop.product_price,
            type: 'final'
          }
        })
      })
  }

  return {
    init: function () {
      // If Apple Pay is available show the button otherwise show the error
      if (_applePayAvailable()) {
        // Notice we are using the functions from our UI controller
        uiController.displayApplePayButton()
      } else {
        uiController.hideApplePayButton()
        uiController.displayErrorMessage()
      }

      // Set the onClick listener on the Apple Pay button
      _setButtonClickListener()
    }
  }
})(applePayUiController) // passing the UI controller

// Initialise the Apple Pay controller and let the magic happen
applePayController.init()

External CSS

  1. https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.0.2/css/bootstrap.min.css

External JavaScript

  1. https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.2.0/js/bootstrap.min.js