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

StepDomain Verification
1Receive the domain association file from 2C2P “apple-developer-merchantid-domain-association.txt”
2Host it to the location specified by 2C2P https://{domainname}/.well-known/apple-developer-merchantid-domain-association.txt

For merchant WITH apple developer account

StepDomain Verification with cert request
1Receive certSigningRequest file from 2C2P, merchant identity certSigningRequest is available upon request from 2C2P, or follow Apple’s Guide
- {merchantId}_payment.certSigningRequest
2Complete payment certSigningRequest at apple developer portal, download “apple_pay.cer” and provide to 2C2P
3Verify merchant domain at apple developer portal. Refer Guide Here

1. Prepare Payment Token Request

To prepare a payment token request, refer to the required parameters below.

📘

Please refer to: Payment Token API Request

Pre Requisite
1. MerchantID, secret code & CurrencyCode are provided by 2C2P.
2. For PaymentChannel, Fill in APPLEPAY
{
  "merchantID": "702702000000000",
  "invoiceNo": "220920084480",
  "description": "V4 Test",
  "amount": "120",
  "currencyCode": "SGD",
  "paymentChannel": ["APPLEPAY"]
}

2. Receive Payment Token Response

To receive a payment token response, refer to the sample payment token response below.

📘

Please refer to: Payment Token API Response

{
  "paymentToken": "kSAops9Zwhos8hSTSeLTUZhBH+ZITpUoBizPNDc+wcpzY5UL2WSG9ky0YSUNT8qJGNWjmT3oMAiGqa1banJ6MGkXkxDM41GKEAw/s0yiR05aTx1QTz7946Wewm7amkCx",
  "respCode": "0000",
  "respDesc": "Success"
}

3. Validation of Payment Token

Proceed only when the parameter "respCode" is "0000". Otherwise, terminate the payment process. Refer to the Payment Response Code below.

🚧

Please refer to: Payment Response Code

4. Front End Implementation

4.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>

4.2 Prepare Java Script to handle Event & Function.

ObjectDescriptio
const paymentRequestPayment Request Parameter provided by merchant.
const session = new ApplePaySession()Apple Pay Session.
JS Function & EventDescription
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: "https://sandbox-pgw-ui.2c2p.com/payment/4.3/doPayment",
                            dataType: "json",
                            type: "POST",
                            data: {
                              "payment": {
                                "data": {
                                  "token": encodedString, //Apple Pay Token
                                  "name": " Terrance",
                                  "email": "[email protected]"
                                },
                                "code": {
                                  "channelCode": "APPLEPAY"
                                }
                              },
                              "responseReturnUrl": "https://pgw-ui.2c2p.com/payment/4.3/#/info/",
                              "clientIP": "116.88.44.153",
                              "paymentToken": "kSAops9Zwhos8hSTSeLTUVOcjgK4Cgv74lr26l90Ar0fr2OeHLgT0u1DHlBeYCZzghmE+UOooat4AGOoS+0ujukbBteCzwIansbend5nSAVieNGbLWn2UWb2NltoNiEW",
                              "clientID": "62b9061f-cd93-4ee8-89cc-a8ffd09e8a95",
                            },
                            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>

5.Merchant Back end Implementation

5.1 Prepare a function to validate merchant with apple.

FunctionDescription
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.
[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 });
  }
}

6. Prepare Do Payment Request

Merchants must call the Do Payment API to request for payment. To prepare a payment request, refer to the sample payment request below.

Pre Requisite :
1. Payment Token from Payment Token API
2. ChannelCode APPLEPAY
3. Parameter token get from frontend parameter payment.token.paymentData
4. For the parameter name & `email

📘

Please refer to: Do Payment API Request

{
  "payment": {
    "data": {
      "token": "eyJkYXRhIjoiZnpDZ2h4aUVBejU2NzcwS0QzakZHMHRXaXB6VHVqbEFjOTQ2SGhvWFBBVEdmVStwTXRJR3RJbFNOVk5YdjZOWWs5UXRiT0lFV09XQXBuaTVzUy9UWGZIcE5jQTUzT0c5bU9xRWtRcnlzKy...", //Apple Pay Token
      "name": " Terrance",
      "email": "[email protected]"
    },
    "code": {
      "channelCode": "APPLEPAY"
    }
  },
  "responseReturnUrl": "https://pgw-ui.2c2p.com/payment/4.3/#/info/",
  "clientIP": "116.88.44.153",
  "paymentToken": "kSAops9Zwhos8hSTSeLTUVOcjgK4Cgv74lr26l90Ar0fr2OeHLgT0u1DHlBeYCZzghmE+UOooat4AGOoS+0ujukbBteCzwIansbend5nSAVieNGbLWn2UWb2NltoNiEW",
  "clientID": "62b9061f-cd93-4ee8-89cc-a8ffd09e8a95",
}

7. Receive Do Payment Response

To receive a payment response, refer to the the sample payment response below.

📘

Please refer to: Do Payment API Response

{
	"invoiceNo": "280520075921",
	"channelCode": "APPLEPAY",
	"respCode": "2000",
	"respDesc": "Transaction is completed, please do payment inquiry request for full payment information."
}

8. Receive Payment Response via backend API

📘

Please refer to: Payment Response - Backend API

The parameter "backendReturnUrl" that was previously sent via Payment Token Request is the merchant endpoint that will receive the backend notification. If the parameter "backendReturnUrl" is not set, the system will obtain the backend return URL from the merchant profile set in 2C2P's merchant portal by default.

{
  "merchantID": "702702000000000",
  "invoiceNo": "220920084480",
  "cardNo": "",
  "amount": 120.0,
  "userDefined1": "",
  "userDefined2": "",
  "userDefined3": "",
  "userDefined4": "",
  "userDefined5": "",
  "currencyCode": "SGD",
  "cardToken": "",
  "recurringUniqueID": "",
  "tranRef": "3257744",
  "referenceNo": "3135918",
  "approvalCode": "",
  "eci": "  ",
  "transactionDateTime": "20200922101226",
  "agentCode": "CITI",
  "channelCode": "VI",
  "issuerCountry": "",
  "installmentMerchantAbsorbRate": null,
  "respCode": "0000",
  "respDesc": "Transaction is successful."
}

9. Receive Payment Response via browser redirection

📘

Please refer to: Payment Response - Frontend API

The parameter "frontendReturnUrl" that was previously sent via Payment Token Request is the merchant page that customers will be redirected to. If the parameter "frontendReturnUrl" is not set, the system will obtain the front end return URL from the merchant profile set in the 2C2P merchant portal by default. Refer to the sample response returned below.

{
	"invoiceNo": "220920084480",
	"channelCode": "APPLEPAY",
	"respCode": "2000",
	"respDesc": "Transaction is completed, please do payment inquiry request for full payment information."
}

10. Payment Inquiry to retrieve payment information

For merchants who do not implement "Receive Payment Response via backend API", you are required to call to the Payment Inquiry API to receive the payment response.

To prepare a payment inquiry request, refer to the sample payment inquiry request below.

📘

Please refer to: Payment Inquiry API Request

{
  "merchantID": "702792000000000",
  "invoiceNo": "220920084480",
  "locale": "en"
}

11. Receive Payment Inquiry Response

To receive a payment inquiry response, refer to the sample payment inquiry response below.

📘

Please refer to: Payment Inquiry API Response

{
  "merchantID": "702702000000000",
  "invoiceNo": "220920084480",
  "cardNo": "",
  "amount": 120.0,
  "userDefined1": "",
  "userDefined2": "",
  "userDefined3": "",
  "userDefined4": "",
  "userDefined5": "",
  "currencyCode": "SGD",
  "cardToken": "",
  "recurringUniqueID": "",
  "tranRef": "3257744",
  "referenceNo": "3135918",
  "approvalCode": "",
  "eci": "  ",
  "transactionDateTime": "20200922101226",
  "agentCode": "CITI",
  "channelCode": "VI",
  "issuerCountry": "",
  "installmentMerchantAbsorbRate": null,
  "respCode": "0000",
  "respDesc": "Transaction is successful."
}