├── .devcontainer └── devcontainer.json ├── .github └── workflows │ ├── deploy.yml │ └── lint.yml ├── .gitignore ├── .vscode └── settings.json ├── README.md └── advanced-integration ├── .env.example ├── .eslintrc.json ├── .gitignore ├── .npmrc ├── .well-known └── apple-developer-domain-association ├── Dockerfile ├── package.json ├── paypal-api.js ├── public └── app.js ├── server.js └── views └── checkout.ejs /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | // For more details, see https://aka.ms/devcontainer.json. 2 | { 3 | "name": "Apple Pay", 4 | "workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}/", 5 | "image": "mcr.microsoft.com/devcontainers/javascript-node:20", 6 | "postCreateCommand": "cd advanced-integration && npm install", 7 | "postAttachCommand": { 8 | "Start server": "cd advanced-integration && CI=true npx kill-port --yes 8888 && npm start" 9 | }, 10 | "forwardPorts": [ 11 | 8888 12 | ], 13 | "portsAttributes": { 14 | "8888": { 15 | "label": "Preview of Apple Pay Flow", 16 | "onAutoForward": "openBrowser" 17 | } 18 | }, 19 | "secrets": { 20 | "CLIENT_ID": { 21 | "description": "Sandbox client ID of the application.", 22 | "documentationUrl": "https://developer.paypal.com/dashboard/applications/sandbox" 23 | }, 24 | "APP_SECRET": { 25 | "description": "Sandbox secret of the application.", 26 | "documentationUrl": "https://developer.paypal.com/dashboard/applications/sandbox" 27 | }, 28 | "MERCHANT_ID":{ 29 | "description": "Sandbox merchant ID of the application.", 30 | "documentationUrl": "https://www.sandbox.paypal.com/businessmanage/account/aboutBusiness" 31 | } 32 | }, 33 | "customizations": { 34 | "vscode": { 35 | "extensions": [ 36 | "vsls-contrib.codetour" 37 | ], 38 | "settings": { 39 | "git.openRepositoryInParentFolders": "always", 40 | "files.exclude": { 41 | "**/.devcontainer": true 42 | } 43 | } 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | name: Deploy 2 | run-name: ${{github.actor}} is deploying applepay test app 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | deploy-advanced-integration: 10 | name: This Deploys the Latest version of Advanced Integration 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v2 14 | - uses: akhileshns/heroku-deploy@v3.7.8 # This is the action 15 | with: 16 | heroku_api_key: ${{secrets.HEROKU_API_KEY}} 17 | heroku_app_name: applepay-paypal-js-sdk # Must be unique in Heroku 18 | heroku_email: ${{secrets.HEROKU_EMAIL}} 19 | appdir: "advanced-integration" 20 | healthcheck: "https://applepay-paypal-js-sdk.herokuapp.com/health" 21 | usedocker: true 22 | dontuseforce: false 23 | docker_build_args: | 24 | NODE_ENV 25 | CLIENT_ID 26 | APP_SECRET 27 | CLIENT_SECRET 28 | MERCHANT_ID 29 | env: 30 | NODE_ENV: staging 31 | CLIENT_ID: ${{secrets.CLIENT_ID}} 32 | APP_SECRET: ${{secrets.APP_SECRET}} 33 | CLIENT_SECRET: ${{secrets.CLIENT_SECRET}} 34 | MERCHANT_ID: ${{secrets.MERCHANT_ID}} -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: Lint 2 | on: [push, pull_request] 3 | jobs: 4 | build-advanced-integration: 5 | runs-on: ubuntu-latest 6 | steps: 7 | - uses: actions/checkout@v2 8 | - uses: actions/setup-node@v3 9 | with: 10 | node-version: '16' 11 | - name: Install JS Dependencies 12 | run: cd advanced-integration && npm install 13 | - name: Run ESLint 14 | run: cd advanced-integration && npm run lint 15 | 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .env 2 | .idea 3 | .DS_STORE 4 | .eslintcache 5 | *.swp 6 | *~ 7 | node_modules 8 | dist 9 | .cache 10 | 11 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "cSpell.words": [ 3 | "appdir", 4 | "appl", 5 | "buttonstyle", 6 | "onpaymentauthorized", 7 | "onpaymentmethodselected", 8 | "onvalidatemerchant", 9 | "usedocker" 10 | ] 11 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PayPal ApplePay Example Code 2 | 3 | Examples from the official [PayPal Developer Docs for ApplePay](https://developer.paypal.com/docs/checkout/apm/apple-pay/). 4 | 5 | ## Introduction and Overview 6 | 7 | This repository contains Integration Example for ApplePay 8 | 9 | 10 | - [Advanced integration](./advanced-integration/) 11 | - Build and customize Applepay Integration with the custom Applepay Component 12 | 13 | 14 | ### The PayPal JavaScript SDK 15 | 16 | These examples use the [PayPal JavaScript SDK](https://developer.paypal.com/sdk/js/) to display ApplePay checkout button for your buyers 17 | 18 | The SDK has several [configuration options](https://developer.paypal.com/sdk/js/configuration/) available. The examples in this repository provide the most minimal example possible to complete a successful transaction. 19 | 20 | ## Know before you code 21 | 22 | ### Setup a PayPal Account 23 | 24 | To get started with standard checkout, you'll need a developer, personal, or business account. 25 | 26 | [Sign Up](https://www.paypal.com/signin/client?flow=provisionUser) or [Log In](https://www.paypal.com/signin?returnUri=https%253A%252F%252Fdeveloper.paypal.com%252Fdeveloper%252Fapplications&intent=developer) 27 | 28 | You'll then need to visit the [Developer Dashboard](https://developer.paypal.com/dashboard/) to obtain credentials and to 29 | make sandbox accounts. 30 | 31 | ### Create an Application 32 | 33 | Once you've setup a PayPal account, you'll need to obtain a **Client ID** and **Secret**. [Create a sandbox application](https://developer.paypal.com/dashboard/applications/sandbox/create). 34 | 35 | ### Have Node.js installed 36 | 37 | These examples will ask you to run commands like `npm install` and `npm start`. 38 | 39 | You'll need a version of node >= 14 which can be downloaded from the [Node.js website](https://nodejs.org/en/download/). 40 | 41 | ### Hosted Example 42 | See a [hosted version ](https://applepay-paypal-js-sdk.herokuapp.com)of the sample 43 | 44 | ## PayPal Codespaces 45 | 46 | PayPal codespaces require a client ID and client secret for your app. 47 | 48 | ### Link to codespaces 49 | 50 | [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/paypal-examples/applepay) 51 | 52 | ### Register codespaces domain 53 | * After your codespaces is ready, copy the codespaces domain URL. 54 | * Login to the [PayPal Sandbox Business Account - Payment methods](https://www.sandbox.paypal.com/businessmanage/account/payments). 55 | * Click on "Manage Apple Pay" link. 56 | * Click on "Add Domain" button. 57 | * Add your fully qualified codespaces domain in "Add your website" textbox. 58 | * Click on "Register Domain". 59 | * Reload your codespaces. 60 | 61 | 62 | ### Learn more 63 | 64 | You can read more about codespaces in the [PayPal Developer Docs](https://developer.paypal.com/api/rest/sandbox/codespaces). 65 | 66 | ### Feedback 67 | 68 | * To report a bug or suggest a new feature, create an [issue in GitHub](https://github.com/paypal-examples/paypaldevsupport/issues/new/choose). 69 | * To submit feedback, go to [PayPal Developer Docs](https://developer.paypal.com/api/rest/sandbox/codespaces) and select the "Feedback" tab 70 | -------------------------------------------------------------------------------- /advanced-integration/.env.example: -------------------------------------------------------------------------------- 1 | # Create an application to obtain credentials at 2 | # https://developer.paypal.com/dashboard/applications/sandbox 3 | 4 | CLIENT_ID="YOUR_PAYPAL_CLIENT_ID_GOES_HERE" 5 | APP_SECRET="YOUR_SECRET_GOES_HERE" 6 | MERCHANT_ID="YOUR_MERCHANT_ID_GOES_HERE" 7 | ``` 8 | -------------------------------------------------------------------------------- /advanced-integration/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | 3 | "extends": "eslint:recommended", 4 | "env": { 5 | "browser": true, 6 | "node": true, 7 | "es6": true 8 | }, 9 | "parserOptions": { 10 | "ecmaVersion": 2020, 11 | "sourceType": "module" 12 | }, 13 | "globals": { 14 | "paypal": true, 15 | "swal": true 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /advanced-integration/.gitignore: -------------------------------------------------------------------------------- 1 | .env 2 | .idea 3 | .DS_STORE 4 | .eslintcache 5 | *.swp 6 | *~ 7 | node_modules 8 | dist 9 | .cache 10 | 11 | -------------------------------------------------------------------------------- /advanced-integration/.npmrc: -------------------------------------------------------------------------------- 1 | registry=https://registry.npmjs.org/ 2 | package-lock=false 3 | -------------------------------------------------------------------------------- /advanced-integration/.well-known/apple-developer-domain-association: -------------------------------------------------------------------------------- 1 | 7B227073704964223A2246354246304143324336314131413238313043373531453439333444414433384346393037313041303935303844314133453241383436314141363232414145222C2276657273696F6E223A312C22637265617465644F6E223A313633343737323736393531342C227369676E6174757265223A223330383030363039326138363438383666373064303130373032613038303330383030323031303133313066333030643036303936303836343830313635303330343032303130353030333038303036303932613836343838366637306430313037303130303030613038303330383230336533333038323033383861303033303230313032303230383463333034313439353139643534333633303061303630383261383634386365336430343033303233303761333132653330326330363033353530343033306332353431373037303663363532303431373037303663363936333631373436393666366532303439366537343635363737323631373436393666366532303433343132303264323034373333333132363330323430363033353530343062306331643431373037303663363532303433363537323734363936363639363336313734363936663665323034313735373436383666373236393734373933313133333031313036303335353034306130633061343137303730366336353230343936653633326533313062333030393036303335353034303631333032353535333330316531373064333133393330333533313338333033313333333233353337356131373064333233343330333533313336333033313333333233353337356133303566333132353330323330363033353530343033306331633635363336333264373336643730326436323732366636623635373232643733363936373665356635353433333432643530353234663434333131343330313230363033353530343062306330623639346635333230353337393733373436353664373333313133333031313036303335353034306130633061343137303730366336353230343936653633326533313062333030393036303335353034303631333032353535333330353933303133303630373261383634386365336430323031303630383261383634386365336430333031303730333432303030346332313537376564656264366337623232313866363864643730393061313231386463376230626436663263323833643834363039356439346166346135343131623833343230656438313166333430376538333333316631633534633366376562333232306436626164356434656666343932383938393365376330663133613338323032313133303832303230643330306330363033353531643133303130316666303430323330303033303166303630333535316432333034313833303136383031343233663234396334346639336534656632376536633466363238366333666132626266643265346233303435303630383262303630313035303530373031303130343339333033373330333530363038326230363031303530353037333030313836323936383734373437303361326632663666363337333730326536313730373036633635326536333666366432663666363337333730333033343264363137303730366336353631363936333631333333303332333038323031316430363033353531643230303438323031313433303832303131303330383230313063303630393261383634383836663736333634303530313330383166653330383163333036303832623036303130353035303730323032333038316236306338316233353236353663363936313665363336353230366636653230373436383639373332303633363537323734363936363639363336313734363532303632373932303631366537393230373036313732373437393230363137333733373536643635373332303631363336333635373037343631366536333635323036663636323037343638363532303734363836353665323036313730373036633639363336313632366336353230373337343631366536343631373236343230373436353732366437333230363136653634323036333666366536343639373436393666366537333230366636363230373537333635326332303633363537323734363936363639363336313734363532303730366636633639363337393230363136653634323036333635373237343639363636393633363137343639366636653230373037323631363337343639363336353230373337343631373436353664363536653734373332653330333630363038326230363031303530353037303230313136326136383734373437303361326632663737373737373265363137303730366336353265363336663664326636333635373237343639363636393633363137343635363137353734363836663732363937343739326633303334303630333535316431663034326433303262333032396130323761303235383632333638373437343730336132663266363337323663326536313730373036633635326536333666366432663631373037303663363536313639363336313333326536333732366333303164303630333535316430653034313630343134393435376462366664353734383138363839383937363266376535373835303765373962353832343330306530363033353531643066303130316666303430343033303230373830333030663036303932613836343838366637363336343036316430343032303530303330306130363038326138363438636533643034303330323033343930303330343630323231303062653039353731666537316531653733356235356535616661636234633732666562343435663330313835323232633732353130303262363165626436663535303232313030643138623335306135646436646436656231373436303335623131656232636538376366613365366166366362643833383038393064633832636464616136333330383230326565333038323032373561303033303230313032303230383439366432666266336139386461393733303061303630383261383634386365336430343033303233303637333131623330313930363033353530343033306331323431373037303663363532303532366636663734323034333431323032643230343733333331323633303234303630333535303430623063316434313730373036633635323034333635373237343639363636393633363137343639366636653230343137353734363836663732363937343739333131333330313130363033353530343061306330613431373037303663363532303439366536333265333130623330303930363033353530343036313330323535353333303165313730643331333433303335333033363332333333343336333333303561313730643332333933303335333033363332333333343336333333303561333037613331326533303263303630333535303430333063323534313730373036633635323034313730373036633639363336313734363936663665323034393665373436353637373236313734363936663665323034333431323032643230343733333331323633303234303630333535303430623063316434313730373036633635323034333635373237343639363636393633363137343639366636653230343137353734363836663732363937343739333131333330313130363033353530343061306330613431373037303663363532303439366536333265333130623330303930363033353530343036313330323535353333303539333031333036303732613836343863653364303230313036303832613836343863653364303330313037303334323030303466303137313138343139643736343835643531613565323538313037373665383830613265666465376261653464653038646663346239336531333335366435363635623335616532326430393737363064323234653762626130386664373631376365383863623736626236363730626563386538323938346666353434356133383166373330383166343330343630363038326230363031303530353037303130313034336133303338333033363036303832623036303130353035303733303031383632613638373437343730336132663266366636333733373032653631373037303663363532653633366636643266366636333733373033303334326436313730373036633635373236663666373436333631363733333330316430363033353531643065303431363034313432336632343963343466393365346566323765366334663632383663336661326262666432653462333030663036303335353164313330313031666630343035333030333031303166663330316630363033353531643233303431383330313638303134626262306465613135383333383839616134386139396465626562646562616664616362323461623330333730363033353531643166303433303330326533303263613032616130323838363236363837343734373033613266326636333732366332653631373037303663363532653633366636643266363137303730366336353732366636663734363336313637333332653633373236633330306530363033353531643066303130316666303430343033303230313036333031303036306132613836343838366637363336343036303230653034303230353030333030613036303832613836343863653364303430333032303336373030333036343032333033616366373238333531313639396231383666623335633335366361363262666634313765646439306637353464613238656265663139633831356534326237383966383938663739623539396639386435343130643866396465396332666530323330333232646435343432316230613330353737366335646633333833623930363766643137376332633231366439363466633637323639383231323666353466383761376431623939636239623039383932313631303639393066303939323164303030303331383230313863333038323031383830323031303133303831383633303761333132653330326330363033353530343033306332353431373037303663363532303431373037303663363936333631373436393666366532303439366537343635363737323631373436393666366532303433343132303264323034373333333132363330323430363033353530343062306331643431373037303663363532303433363537323734363936363639363336313734363936663665323034313735373436383666373236393734373933313133333031313036303335353034306130633061343137303730366336353230343936653633326533313062333030393036303335353034303631333032353535333032303834633330343134393531396435343336333030643036303936303836343830313635303330343032303130353030613038313935333031383036303932613836343838366637306430313039303333313062303630393261383634383836663730643031303730313330316330363039326138363438383666373064303130393035333130663137306433323331333133303332333033323333333333323334333935613330326130363039326138363438383666373064303130393334333131643330316233303064303630393630383634383031363530333034303230313035303061313061303630383261383634386365336430343033303233303266303630393261383634383836663730643031303930343331323230343230623935666665303261316539316665656565396330623239616361656661643465333031396331666237626238313665366631623762343233346666306533353330306130363038326138363438636533643034303330323034343733303435303232303665356233363937366364383733653632623339326330353136633134326362356639303938663330323535656435343938633436393039356133636462346430323231303038396261626335356162626635653037393163633139373562306535383630633937336532336661313266643338343533303930353938343061326363363337303030303030303030303030227D 2 | -------------------------------------------------------------------------------- /advanced-integration/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:16 2 | 3 | ARG CLIENT_ID 4 | ARG CLIENT_SECRET 5 | ARG MERCHANT_ID 6 | ARG NODE_ENV 7 | ARG APP_SECRET 8 | 9 | ENV CLIENT_ID=$CLIENT_ID 10 | ENV CLIENT_SECRET=$CLIENT_SECRET 11 | ENV NODE_ENV=$NODE_ENV 12 | ENV APP_SECRET=$APP_SECRET 13 | ENV MERCHANT_ID=$MERCHANT_ID 14 | 15 | WORKDIR /app 16 | COPY package.json ./ 17 | RUN npm install --production 18 | 19 | COPY . . 20 | 21 | 22 | 23 | CMD ["node", "server.js"] 24 | -------------------------------------------------------------------------------- /advanced-integration/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "paypal-applepay-advanced-integration", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "server.js", 6 | "type": "module", 7 | "scripts": { 8 | "test": "echo \"Error: no test specified\" && exit 1", 9 | "start": "node server.js", 10 | "lint": "eslint . --ext .js,.jsx,.ts,.tsx" 11 | }, 12 | "author": "", 13 | "license": "Apache-2.0", 14 | "dependencies": { 15 | "dotenv": "^16.0.0", 16 | "ejs": "^3.1.6", 17 | "express": "^4.17.3", 18 | "node-fetch": "^3.2.1" 19 | }, 20 | "devDependencies": { 21 | "eslint": "^7.22.0" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /advanced-integration/paypal-api.js: -------------------------------------------------------------------------------- 1 | import fetch from "node-fetch"; 2 | 3 | // set some important variables 4 | const { CLIENT_ID, APP_SECRET, MERCHANT_ID } = process.env; 5 | const base = "https://api-m.sandbox.paypal.com"; 6 | 7 | // call the create order method 8 | export async function createOrder() { 9 | const purchaseAmount = "10.00"; // TODO: pull prices from a database 10 | const accessToken = await generateAccessToken(); 11 | const url = `${base}/v2/checkout/orders`; 12 | const response = await fetch(url, { 13 | method: "post", 14 | headers: { 15 | "Content-Type": "application/json", 16 | Authorization: `Bearer ${accessToken}`, 17 | }, 18 | body: JSON.stringify({ 19 | intent: "CAPTURE", 20 | purchase_units: [ 21 | { 22 | amount: { 23 | currency_code: "USD", 24 | value: purchaseAmount, 25 | }, 26 | payee: { 27 | merchant_id: MERCHANT_ID, 28 | } 29 | }, 30 | ], 31 | }), 32 | }); 33 | 34 | return handleResponse(response); 35 | } 36 | 37 | // capture payment for an order 38 | export async function capturePayment(orderId) { 39 | const accessToken = await generateAccessToken(); 40 | const url = `${base}/v2/checkout/orders/${orderId}/capture`; 41 | const response = await fetch(url, { 42 | method: "post", 43 | headers: { 44 | "Content-Type": "application/json", 45 | Authorization: `Bearer ${accessToken}`, 46 | }, 47 | }); 48 | 49 | return handleResponse(response); 50 | } 51 | 52 | // generate access token 53 | export async function generateAccessToken() { 54 | const auth = Buffer.from(CLIENT_ID + ":" + APP_SECRET).toString("base64"); 55 | const response = await fetch(`${base}/v1/oauth2/token`, { 56 | method: "post", 57 | body: "grant_type=client_credentials", 58 | headers: { 59 | Authorization: `Basic ${auth}`, 60 | }, 61 | }); 62 | const jsonData = await handleResponse(response); 63 | return jsonData.access_token; 64 | } 65 | 66 | // generate client token 67 | export async function generateClientToken() { 68 | const accessToken = await generateAccessToken(); 69 | const response = await fetch(`${base}/v1/identity/generate-token`, { 70 | method: "post", 71 | headers: { 72 | Authorization: `Bearer ${accessToken}`, 73 | "Accept-Language": "en_US", 74 | "Content-Type": "application/json", 75 | }, 76 | }); 77 | console.log('response', response.status) 78 | const jsonData = await handleResponse(response); 79 | return jsonData.client_token; 80 | } 81 | 82 | async function handleResponse(response) { 83 | if (response.status === 200 || response.status === 201) { 84 | return response.json(); 85 | } 86 | 87 | const errorMessage = await response.text(); 88 | throw new Error(errorMessage); 89 | } 90 | -------------------------------------------------------------------------------- /advanced-integration/public/app.js: -------------------------------------------------------------------------------- 1 | 2 | async function setupApplepay() { 3 | const applepay = paypal.Applepay(); 4 | const { 5 | isEligible, 6 | countryCode, 7 | currencyCode, 8 | merchantCapabilities, 9 | supportedNetworks, 10 | } = await applepay.config(); 11 | 12 | if (!isEligible) { 13 | throw new Error("applepay is not eligible"); 14 | } 15 | 16 | document.getElementById("applepay-container").innerHTML = 17 | ''; 18 | 19 | document.getElementById("btn-appl").addEventListener("click", onClick); 20 | 21 | async function onClick() { 22 | console.log({ merchantCapabilities, currencyCode, supportedNetworks }) 23 | 24 | const paymentRequest = { 25 | countryCode, 26 | currencyCode: 'USD', 27 | merchantCapabilities, 28 | supportedNetworks, 29 | requiredBillingContactFields: [ 30 | "name", 31 | "phone", 32 | "email", 33 | "postalAddress", 34 | ], 35 | requiredShippingContactFields: [ 36 | ], 37 | total: { 38 | label: "Demo (Card is not charged)", 39 | amount: "10.00", 40 | type: "final", 41 | }, 42 | }; 43 | 44 | // eslint-disable-next-line no-undef 45 | let session = new ApplePaySession(4, paymentRequest); 46 | 47 | session.onvalidatemerchant = (event) => { 48 | applepay 49 | .validateMerchant({ 50 | validationUrl: event.validationURL, 51 | }) 52 | .then((payload) => { 53 | session.completeMerchantValidation(payload.merchantSession); 54 | }) 55 | .catch((err) => { 56 | console.error(err); 57 | session.abort(); 58 | }); 59 | }; 60 | 61 | session.onpaymentmethodselected = () => { 62 | session.completePaymentMethodSelection({ 63 | newTotal: paymentRequest.total, 64 | }); 65 | }; 66 | 67 | session.onpaymentauthorized = async (event) => { 68 | try { 69 | /* Create Order on the Server Side */ 70 | const orderResponse = await fetch(`/api/orders`,{ 71 | method:'POST', 72 | headers : { 73 | 'Content-Type': 'application/json' 74 | } 75 | }) 76 | if(!orderResponse.ok) { 77 | throw new Error("error creating order") 78 | } 79 | 80 | const { id } = await orderResponse.json() 81 | console.log({ id }) 82 | /** 83 | * Confirm Payment 84 | */ 85 | await applepay.confirmOrder({ orderId: id, token: event.payment.token, billingContact: event.payment.billingContact , shippingContact: event.payment.shippingContact }); 86 | 87 | /* 88 | * Capture order (must currently be made on server) 89 | */ 90 | await fetch(`/api/orders/${id}/capture`, { 91 | method: 'POST', 92 | }); 93 | 94 | session.completePayment({ 95 | status: window.ApplePaySession.STATUS_SUCCESS, 96 | }); 97 | } catch (err) { 98 | console.error(err); 99 | session.completePayment({ 100 | status: window.ApplePaySession.STATUS_FAILURE, 101 | }); 102 | } 103 | }; 104 | 105 | session.oncancel = () => { 106 | console.log("Apple Pay Cancelled !!") 107 | } 108 | 109 | session.begin(); 110 | } 111 | } 112 | 113 | document.addEventListener("DOMContentLoaded", () => { 114 | 115 | // eslint-disable-next-line no-undef 116 | if(ApplePaySession?.supportsVersion(4) && ApplePaySession?.canMakePayments()) { 117 | setupApplepay().catch(console.error); 118 | } 119 | }); 120 | -------------------------------------------------------------------------------- /advanced-integration/server.js: -------------------------------------------------------------------------------- 1 | import "dotenv/config"; 2 | import express from "express"; 3 | import * as paypal from "./paypal-api.js"; 4 | const { PORT = 8888 } = process.env; 5 | 6 | const app = express(); 7 | app.set("view engine", "ejs"); 8 | app.use(express.static("public")); 9 | 10 | // render checkout page with client id & unique client token 11 | app.get("/", async (req, res) => { 12 | const clientId = process.env.CLIENT_ID, merchantId = process.env.MERCHANT_ID; 13 | try { 14 | const clientToken = await paypal.generateClientToken(); 15 | res.render("checkout", { clientId, clientToken, merchantId }); 16 | } catch (err) { 17 | res.status(500).send(err.message); 18 | } 19 | }); 20 | 21 | // create order 22 | app.post("/api/orders", async (req, res) => { 23 | try { 24 | const order = await paypal.createOrder(); 25 | res.json(order); 26 | } catch (err) { 27 | res.status(500).send(err.message); 28 | } 29 | }); 30 | 31 | // capture payment 32 | app.post("/api/orders/:orderID/capture", async (req, res) => { 33 | const { orderID } = req.params; 34 | try { 35 | const captureData = await paypal.capturePayment(orderID); 36 | res.json(captureData); 37 | } catch (err) { 38 | res.status(500).send(err.message); 39 | } 40 | }); 41 | 42 | // health check 43 | app.get("/check", (req, res) => { 44 | res.json({ 45 | message: "ok", 46 | env: process.env.NODE_ENV, 47 | clientId: process.env.CLIENT_ID, 48 | appSecret: process.env.APP_SECRET || "Couldn't load App Secret", 49 | clientSecret: process.env.CLIENT_SECRET, 50 | merchantId: process.env.MERCHANT_ID 51 | }) 52 | }) 53 | 54 | app.listen(PORT, () => { 55 | console.log(`Server listening at http://localhost:${PORT}/`); 56 | }); 57 | -------------------------------------------------------------------------------- /advanced-integration/views/checkout.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 30 | 31 | 32 | 33 | 34 | 38 | 39 | 45 | 46 | 47 | 48 |
49 |

Sample Applepay Integration

50 |
Basic Integration (no amount breakdown / no shipping)
51 |
52 | 53 |
You wont be charged any money. Try with Apple Pay Test Cards on Sandbox. You can find them here
54 |
55 | 56 | 57 | 58 | --------------------------------------------------------------------------------