Apple Pay

Apple Pay is a mobile payment and digital wallet service by Apple Inc. that allows users to make payments in person, in iOS apps, and on the web using Safari. It is supported on the iPhone, Apple Watch, iPad, and Mac. It digitizes and can replace a credit or debit card chip and PIN transaction at a contactless-capable point-of-sale terminal.

🚧

Support devices & browser

Refer Apple Pay is compatible with these devices

 
Prerequisites
--


 
Configuration
--


For merchant WITHOUT apple developer account

Step

Domain Verification

1

Receive the domain association file from 2C2P “apple-developer-merchantid-domain-association.txt”

2

Host it to the location specified by 2C2P https://{domainname}/.well-known/apple-developer-merchantid-domain-association.txt

For merchant WITH apple developer account

Step

Domain Verification with cert request

1

Receive certSigningRequest file from 2C2P, merchant identity certSigningRequest is available upon request from 2C2P, or follow Apple’s Guide

  • {merchantId}_payment.certSigningRequest

2

Complete payment certSigningRequest at apple developer portal, download “apple_pay.cer” and provide to 2C2P

3

Verify merchant domain at apple developer portal. Refer Guide Here

 

Integration Guide


Below show sample codes and explanation of each functions.

👍

Provided Sample Code

Download here

🚧

Environment

Please refer Demo & Live Endpoint.

 
For Front End Implementation

  1. Render the component of apple pay button in html. Refer below sample code.
<p>Buy with ApplePay</p>
<p>
  Compatible browsers will display an Apple&nbsp;Pay button below.
</p>
<div id="apple-pay-button" onclick="applePayButtonClicked()"></div>

<style>
  #apple-pay-button {
    -webkit-appearance: -apple-pay-button;
    -apple-pay-button-type: plain;
    -apple-pay-button-style: black;
    display: inline-block;
    width: 150px;
    height: 35px;
    vertical-align: middle;
    margin-left: 5px
  }
</style>
  1. Prepare Java Script to handle Event & Function.

Object

Description

const paymentRequest

Payment Request Parameter provided by merchant.

const session = new ApplePaySession()

Apple Pay Session.

JS Function & Event

Description

showApplePayButton()

To load & display the apple pay button,
*Please note that only support device and browser can be load.

applePayButtonClicked()

Function triggered when user click pay button

session.onvalidatemerchant()

This event() use to create a new merchant session and this event should call 2c2p backend to do create a session via Apple Pay

session.completeMerchantValidation()

When Apple Pay return session response, this event should call with the session response as parameter.

session.onpaymentauthorized()

This event() use to make a payment via 2c2p secure pay. Required to get Apple Pay Payment Token Data and pass it to backend to process 2c2p Secure Pay. Step as below:-

  1. Retrieve data payment.token.paymentData
  2. Encode it.
  3. Pass in to parameter mobilePaymentToken

session.completePayment()

This event is call when payment has completed.

When payment is success, complete payment with ApplePaySession.STATUS_SUCCESS
When payment is failed, complete payment with ApplePaySession.STATUS_FAILURE

<script type="text/javascript">
            
            document.addEventListener('DOMContentLoaded', () => {
                if (window.ApplePaySession) {
                    if (ApplePaySession.canMakePayments) {
                        showApplePayButton();
                    }
                }
            });

            function showApplePayButton() {
                HTMLCollection.prototype[Symbol.iterator] = Array.prototype[Symbol.iterator];
                const buttons = document.getElementsByClassName("apple-pay-button");
                for (let button of buttons) {
                    button.className += " visible";
                }
            }

            function applePayButtonClicked() {
                const paymentRequest = {
                    countryCode: "SG", // A2
                    currencyCode: "SGD",
                    lineItems: [
                        {
                            label: "item 1",
                            amount: 99.00
                        }
                    ],
                    total: {
                        label: "Total",
                        amount: 99.00
                    },
                    supportedNetworks: ['masterCard', 'visa'],
                    merchantCapabilities: ['supports3DS', 'supportsCredit', 'supportsDebit']
                };

                const session = new ApplePaySession(3, paymentRequest);

                /**
                * Merchant Validation
                * We call our merchant session endpoint, passing the URL to use
                */
                session.onvalidatemerchant = (event) => {
                    const validationURL = event.validationURL;

                      $.ajax({
                        url: "@Url.Action("ValidateMerchant","Home")",
                        dataType: "json",
                        type: "POST",
                        data: { 'validationUrl': validationURL },
                        success: function (response) {
                            session.completeMerchantValidation(JSON.parse(response));
                        },
                        error: function (status) {
                            console.log(JSON.stringify(status));
                            session.abort();
                        }
                    });

                };

                /**
                * Payment Authorization
                * Here you receive the encrypted payment data. You would then send it
                * on to your payment provider for processing, and return an appropriate
                * status in session.completePayment()
                */
                session.onpaymentauthorized = (event) => {
                    // Send payment for processing...
                    const payment = event.payment;
                    var requestData = JSON.stringify((payment.token.paymentData));
                    var encodedString = Base64.encode(requestData);
                    $.ajax({
                            url: "@Url.Action("Pay","Home")",
                            dataType: "json",
                            type: "POST",
                            data: { 'mobilePaymentToken': encodedString },
                            error: function (ts) {
                                console.log(ts);
                            },
                            success: function (response) {
                                console.log(response);
                                switch (response.status)
                                {
                                    case 'ERROR':
                                        session.completePayment(ApplePaySession.STATUS_FAILURE);
                                        break;
                                    case 'SUCCESS':
                                        session.completePayment(ApplePaySession.STATUS_SUCCESS);
                                        break;
                                    default:
                                        session.completePayment(ApplePaySession.STATUS_FAILURE);
                                        break;
                                }

                            }
                    });

                    // this is the place where calling sending to authorization happened
                    // ...return a status and redirect to a confirmation page
                }

                // All our handlers are setup - start the Apple Pay payment
                session.begin();
            }
</script>

 
For Back End Implementation

  1. Prepare a function to validate merchant with apple.

Function

Description

ValidateMerchant(AppleValidateMerchantSessionModel appleValidateMerchant)

This is the backend function use for request merchant session via Apple Pay

session.onvalidatemerchant() event from front end will call this function.

Pay(MobileTokenPaymentModel request)

This is the backend function to create & process 2c2p Secure Pay.

session.onpaymentauthorized() event from front end will call this function.

[HttpPost]
public async Task<ActionResult> ValidateMerchant(AppleValidateMerchantSessionModel appleValidateMerchant)
{
  // TODO add additional mapping validation url from Apple Pay
  if (!Uri.TryCreate(appleValidateMerchant.ValidationUrl, UriKind.Absolute, out var requestUri))
  {
    return BadRequest(new ValidationResponse() { status = "ERROR", data = "Invalid validation url" });
  }

  try
  {
    X509Certificate2 privateKey = new X509Certificate2(config.Value.PrivateKeyLocation, config.Value.PrivateKeyPassword, X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.Exportable);

    AppleMerchantCertificate merchantIdentityCertificate = new AppleMerchantCertificate(privateKey);

    // Create the JSON payload to POST to the Apple Pay merchant validation URL.
    var payloadObj = new AppleMerchantSessionRequest()
    {
      DisplayName = "Awesome Idea Pte Ltd",
      Initiative = "web",
      InitiativeContext = Request.Host.Host,
      MerchantIdentifier = merchantIdentityCertificate.GetMerchantIdentifier()
      };
    string payload = JsonConvert.SerializeObject(payloadObj);
    
    string responsePayload;

    var handler = new HttpClientHandler();
    handler.ClientCertificates.Add(merchantIdentityCertificate.GetCertificate());

    var client = new HttpClient(handler);

    var response = await client.PostAsync(appleValidateMerchant.ValidationUrl, new StringContent(payload, Encoding.UTF8));
    if (response.IsSuccessStatusCode)
    {
      responsePayload = await response.Content.ReadAsStringAsync();
      return Json(responsePayload);
    }
    else
    {
      responsePayload = await response?.Content?.ReadAsStringAsync();
      _logger.LogError($"ValidateMerchant: httperror: {response.StatusCode}, response: {responsePayload} ");

      return BadRequest(new ValidationResponse() { status = "ERROR", data = $"Error: {responsePayload}" });
    }


    }
  catch (Exception ex)
  {
    _logger.LogError($"ValidateMerchant: exception=> {ex.Message}, {ex.ToString()}, IE: {ex.InnerException?.ToString()}");

    return BadRequest(new ValidationResponse() { status = "ERROR", data = ex.Message });
  }
}
  1. Prepare a function to construct & process Secure Pay payment. Below are the specific data required for Apple Pay.

Parameter

Value

PaymentChannel

APPLEPAY

mobilePaymentData

Apple Pay Payment Token retrieve from parameter MobilePaymentToken

Provided sample code to process Secure Pay.

[HttpPost]
public async Task<ActionResult> Pay(MobileTokenPaymentModel request)
{
  string token = request.MobilePaymentToken;

  string url = "https://demo2.2c2p.com/2c2pfrontend/securepayment/mauthpayment.aspx";

  // make payment
  string paymentResponse = string.Empty, error = string.Empty;

  //Merchant Account Information
  string merchantID = "JT01";             //Get MerchantID from 2c2p PGW Dashboard
  string secretKey = "7jYcp4FxFdf0";      //Get SecretKey from 2c2p PGW Dashboard

  //Product Information
  string uniqueTransactionCode = "Invoice" + DateTime.Now.ToString("yyMMddHHmmss");
  string desc = "Test Payment";

  //***** AMOUNT AND CURRENCY WILL GET VALIDATED BY THE API. 
  //      WHEN MISMATCHED BETWEEN APPLEPAY TOKEN and SUBMITTED AMOUNT, PAYMENT WILL NOT BE PROCESSED.

  string amt = "000000001000";            //12 digit format 
  string currencyCode = "702";            //Ref: http://en.wikipedia.org/wiki/ISO_4217



  //Customer Information
  string customerName = "John Doe";
  string country = "SG";

  //Request Information
  string timeStamp = DateTime.Now.ToString("ddMMyyHHmmss");
  string apiVersion = "9.9";

  //Construct payment request message
  string payloadXml = "<PaymentRequest>" +
    "<timeStamp>" + timeStamp + "</timeStamp>" +
    "<merchantID>" + merchantID + "</merchantID>" +
    "<paymentChannel>APPLEPAY</paymentChannel>" +
    "<uniqueTransactionCode>" + uniqueTransactionCode + "</uniqueTransactionCode>" +
    "<desc>" + System.Web.HttpUtility.HtmlEncode(desc) + "</desc>" +
    "<amt>" + amt + "</amt>" +
    "<currencyCode>" + currencyCode + "</currencyCode>" +
    "<panCountry>" + country + "</panCountry>" +
    "<cardholderName>" + customerName + "</cardholderName>" +
    "<mobilePaymentData>" + request.MobilePaymentToken + "</mobilePaymentData>" +
    "</PaymentRequest>";

  string payload = base64Encode(payloadXml);                                //Convert payload to base64  
  string hash = getHMAC2(payload, secretKey);                         //Calculate Hash Value

  string xml = "<PaymentRequest>" +
    "<version>" + apiVersion + "</version>" +                                          //request version number (9.9)
    "<payload>" + payload + "</payload>" +                                             //request payload
    "<signature>" + hash + "</signature>" +                                       //signature
    "</PaymentRequest>";

  string data = base64Encode(xml);                                //Convert payload to base64                    

  var response = await PostPayment(data, url);
  if (response.Item1)
  {
    if (!string.IsNullOrEmpty(response.Item2))
    {
      string clearResult = base64Decode(response.Item2);
      XmlDocument xDocResponse = new XmlDocument();
      xDocResponse.LoadXml(clearResult);
      string respPayload = (xDocResponse.SelectNodes("//payload").Count > 0 ? xDocResponse.SelectSingleNode("//payload").InnerText : "");
      string respSign = (xDocResponse.SelectNodes("//signature").Count > 0 ? xDocResponse.SelectSingleNode("//signature").InnerText : "");

      string respHash = getHMAC2(respPayload, secretKey);                         //Calculate Hash Value

      if (respHash.Equals(respSign, StringComparison.OrdinalIgnoreCase))
      {
        respPayload = base64Decode(respPayload);

        XmlDocument xDocPayload = new XmlDocument();
        xDocPayload.LoadXml(respPayload);

        string respCode = (xDocPayload.SelectNodes("//respCode").Count > 0 ? xDocPayload.SelectSingleNode("//respCode").InnerText : "");

        string paymentStatus = ((respCode == "00") ? "SUCCESS" : "FAILED");
        return Ok(new { status = paymentStatus });
      }
      else
      {
        _logger.LogInformation($"Invalid Signature");

        return BadRequest(new { status = "ERROR", data = "INVALID SIGNATURE" });
      }
    }
    else
    {
      return BadRequest(new { status = "ERROR", data = "EMPTY RESULT" });
    }
  }
  else
  {
    return BadRequest(new { status = "ERROR", data = "UNABLE TO POST" });
  };
}