Learn how to adapt your Android payment app to work with Web Payments and provide a better user experience for customers.


![Eiji Kitamura](https://web.dev/images/authors/agektmr.jpg) Eiji Kitamura [X](https://twitter.com/agektmr) [GitHub](https://github.com/agektmr) [Mastodon](https://infosec.exchange/@agektmr) [Bluesky](https://bsky.app/profile/agektmr.com) ![Yuichi Araki](https://web.dev/images/authors/yaraki.jpg) Yuichi Araki [X](https://twitter.com/yuichi_araki) [GitHub](https://github.com/yaraki)

<br />


Published: May 5, 2020, Last updated: October 16, 2026

<br />

The [Payment Request API](https://www.w3.org/TR/payment-request/) brings to the
web a built-in browser-based interface that allows users to enter required payment
information easier than ever before. The API can also invoke platform-specific payment
apps.


Browser Support

- ![Chrome: 60.](data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='-10 -10 276 276'%3E%3ClinearGradient id='a' x1='145' x2='34' y1='253' y2='61' gradientUnits='userSpaceOnUse'%3E%3Cstop offset='0' stop-color='%231e8e3e'/%3E%3Cstop offset='1' stop-color='%2334a853'/%3E%3C/linearGradient%3E%3ClinearGradient id='b' x1='111' x2='222' y1='254' y2='62' gradientUnits='userSpaceOnUse'%3E%3Cstop offset='0' stop-color='%23fcc934'/%3E%3Cstop offset='1' stop-color='%23fbbc04'/%3E%3C/linearGradient%3E%3ClinearGradient id='c' x1='17' x2='239' y1='80' y2='80' gradientUnits='userSpaceOnUse'%3E%3Cstop offset='0' stop-color='%23d93025'/%3E%3Cstop offset='1' stop-color='%23ea4335'/%3E%3C/linearGradient%3E%3Ccircle cx='128' cy='128' r='64' fill='%23fff'/%3E%3Cpath fill='url(%23a)' d='M96 183a64 64 0 0 1-23-23L17 64a128 128 0 0 0 111 192l55-96a64 64 0 0 1-87 23Z'/%3E%3Cpath fill='url(%23b)' d='M192 128a64 64 0 0 1-9 32l-55 96A128 128 0 0 0 239 64H128a64 64 0 0 1 64 64Z'/%3E%3Ccircle cx='128' cy='128' r='52' fill='%231a73e8'/%3E%3Cpath fill='url(%23c)' d='M96 73a64 64 0 0 1 32-9h111a128 128 0 0 0-222 0l56 96a64 64 0 0 1 23-87Z'/%3E%3C/svg%3E)
- ![Edge: 15.](data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' width='24' height='24' viewBox='0 0 27600 27600'%3E%3ClinearGradient id='A' gradientUnits='userSpaceOnUse'/%3E%3ClinearGradient id='B' x1='6870' x2='24704' y1='18705' y2='18705' xlink:href='%23A'%3E%3Cstop offset='0' stop-color='%230c59a4'/%3E%3Cstop offset='1' stop-color='%23114a8b'/%3E%3C/linearGradient%3E%3ClinearGradient id='C' x1='16272' x2='5133' y1='10968' y2='23102' xlink:href='%23A'%3E%3Cstop offset='0' stop-color='%231b9de2'/%3E%3Cstop offset='.16' stop-color='%231595df'/%3E%3Cstop offset='.67' stop-color='%230680d7'/%3E%3Cstop offset='1' stop-color='%230078d4'/%3E%3C/linearGradient%3E%3CradialGradient id='D' cx='16720' cy='18747' r='9538' xlink:href='%23A'%3E%3Cstop offset='.72' stop-opacity='0'/%3E%3Cstop offset='.95' stop-opacity='.53'/%3E%3Cstop offset='1'/%3E%3C/radialGradient%3E%3CradialGradient id='E' cx='7130' cy='19866' r='14324' gradientTransform='matrix(.14843 -.98892 .79688 .1196 -8759 25542)' xlink:href='%23A'%3E%3Cstop offset='.76' stop-opacity='0'/%3E%3Cstop offset='.95' stop-opacity='.5'/%3E%3Cstop offset='1'/%3E%3C/radialGradient%3E%3CradialGradient id='F' cx='2523' cy='4680' r='20243' gradientTransform='matrix(-.03715 .99931 -2.12836 -.07913 13579 3530)' xlink:href='%23A'%3E%3Cstop offset='0' stop-color='%2335c1f1'/%3E%3Cstop offset='.11' stop-color='%2334c1ed'/%3E%3Cstop offset='.23' stop-color='%232fc2df'/%3E%3Cstop offset='.31' stop-color='%232bc3d2'/%3E%3Cstop offset='.67' stop-color='%2336c752'/%3E%3C/radialGradient%3E%3CradialGradient id='G' cx='24247' cy='7758' r='9734' gradientTransform='matrix(.28109 .95968 -.78353 .22949 24510 -16292)' xlink:href='%23A'%3E%3Cstop offset='0' stop-color='%2366eb6e'/%3E%3Cstop offset='1' stop-color='%2366eb6e' stop-opacity='0'/%3E%3C/radialGradient%3E%3Cpath id='H' d='M24105 20053a9345 9345 0 01-1053 472 10202 10202 0 01-3590 646c-4732 0-8855-3255-8855-7432 0-1175 680-2193 1643-2729-4280 180-5380 4640-5380 7253 0 7387 6810 8137 8276 8137 791 0 1984-230 2704-456l130-44a12834 12834 0 006660-5282c220-350-168-757-535-565z'/%3E%3Cpath id='I' d='M11571 25141a7913 7913 0 01-2273-2137 8145 8145 0 01-1514-4740 8093 8093 0 013093-6395 8082 8082 0 011373-859c312-148 846-414 1554-404a3236 3236 0 012569 1297 3184 3184 0 01636 1866c0-21 2446-7960-8005-7960-4390 0-8004 4166-8004 7820 0 2319 538 4170 1212 5604a12833 12833 0 007684 6757 12795 12795 0 003908 610c1414 0 2774-233 4045-656a7575 7575 0 01-6278-803z'/%3E%3Cpath id='J' d='M16231 15886c-80 105-330 250-330 566 0 260 170 512 472 723 1438 1003 4149 868 4156 868a5954 5954 0 003027-839 6147 6147 0 001133-850 6180 6180 0 001910-4437c26-2242-796-3732-1133-4392-2120-4141-6694-6525-11668-6525-7011 0-12703 5635-12798 12620 47-3654 3679-6605 7996-6605 350 0 2346 34 4200 1007 1634 858 2490 1894 3086 2921 618 1067 728 2415 728 2952s-271 1333-780 1990z'/%3E%3Cuse fill='url(%23B)' xlink:href='%23H'/%3E%3Cuse fill='url(%23D)' opacity='.35' xlink:href='%23H'/%3E%3Cuse fill='url(%23C)' xlink:href='%23I'/%3E%3Cuse fill='url(%23E)' opacity='.4' xlink:href='%23I'/%3E%3Cuse fill='url(%23F)' xlink:href='%23J'/%3E%3Cuse fill='url(%23G)' xlink:href='%23J'/%3E%3C/svg%3E)
- ![Firefox: behind a flag.](data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 512 512'%3E%3Cdefs%3E%3CradialGradient id='ff-b' cx='428.5' cy='55.1' r='501' gradientUnits='userSpaceOnUse'%3E%3Cstop offset='.1' stop-color='%23ffbd4f'/%3E%3Cstop offset='.2' stop-color='%23ffac31'/%3E%3Cstop offset='.3' stop-color='%23ff9d17'/%3E%3Cstop offset='.3' stop-color='%23ff980e'/%3E%3Cstop offset='.4' stop-color='%23ff563b'/%3E%3Cstop offset='.5' stop-color='%23ff3750'/%3E%3Cstop offset='.7' stop-color='%23f5156c'/%3E%3Cstop offset='.8' stop-color='%23eb0878'/%3E%3Cstop offset='.9' stop-color='%23e50080'/%3E%3C/radialGradient%3E%3CradialGradient id='ff-c' cx='245.4' cy='259.9' r='501' gradientUnits='userSpaceOnUse'%3E%3Cstop offset='.3' stop-color='%23960e18'/%3E%3Cstop offset='.3' stop-color='%23b11927' stop-opacity='.7'/%3E%3Cstop offset='.4' stop-color='%23db293d' stop-opacity='.3'/%3E%3Cstop offset='.5' stop-color='%23f5334b' stop-opacity='.1'/%3E%3Cstop offset='.5' stop-color='%23ff3750' stop-opacity='0'/%3E%3C/radialGradient%3E%3CradialGradient id='ff-d' cx='305.8' cy='-58.6' r='363' gradientUnits='userSpaceOnUse'%3E%3Cstop offset='.1' stop-color='%23fff44f'/%3E%3Cstop offset='.3' stop-color='%23ffdc3e'/%3E%3Cstop offset='.5' stop-color='%23ff9d12'/%3E%3Cstop offset='.5' stop-color='%23ff980e'/%3E%3C/radialGradient%3E%3CradialGradient id='ff-e' cx='190' cy='390.8' r='238.6' gradientUnits='userSpaceOnUse'%3E%3Cstop offset='.3' stop-color='%233a8ee6'/%3E%3Cstop offset='.5' stop-color='%235c79f0'/%3E%3Cstop offset='.7' stop-color='%239059ff'/%3E%3Cstop offset='1' stop-color='%23c139e6'/%3E%3C/radialGradient%3E%3CradialGradient id='ff-f' cx='252.2' cy='201.3' r='126.5' gradientTransform='matrix(1 0 0 1 -48 31)' gradientUnits='userSpaceOnUse'%3E%3Cstop offset='.2' stop-color='%239059ff' stop-opacity='0'/%3E%3Cstop offset='.3' stop-color='%238c4ff3' stop-opacity='.1'/%3E%3Cstop offset='.8' stop-color='%237716a8' stop-opacity='.5'/%3E%3Cstop offset='1' stop-color='%236e008b' stop-opacity='.6'/%3E%3C/radialGradient%3E%3CradialGradient id='ff-g' cx='239.1' cy='34.6' r='171.6' gradientUnits='userSpaceOnUse'%3E%3Cstop offset='0' stop-color='%23ffe226'/%3E%3Cstop offset='.1' stop-color='%23ffdb27'/%3E%3Cstop offset='.3' stop-color='%23ffc82a'/%3E%3Cstop offset='.5' stop-color='%23ffa930'/%3E%3Cstop offset='.7' stop-color='%23ff7e37'/%3E%3Cstop offset='.8' stop-color='%23ff7139'/%3E%3C/radialGradient%3E%3CradialGradient id='ff-h' cx='374' cy='-74.3' r='732.2' gradientUnits='userSpaceOnUse'%3E%3Cstop offset='.1' stop-color='%23fff44f'/%3E%3Cstop offset='.5' stop-color='%23ff980e'/%3E%3Cstop offset='.6' stop-color='%23ff5634'/%3E%3Cstop offset='.7' stop-color='%23ff3647'/%3E%3Cstop offset='.9' stop-color='%23e31587'/%3E%3C/radialGradient%3E%3CradialGradient id='ff-i' cx='304.6' cy='7.1' r='536.4' gradientTransform='rotate(84 303 4)' gradientUnits='userSpaceOnUse'%3E%3Cstop offset='0' stop-color='%23fff44f'/%3E%3Cstop offset='.1' stop-color='%23ffe847'/%3E%3Cstop offset='.2' stop-color='%23ffc830'/%3E%3Cstop offset='.3' stop-color='%23ff980e'/%3E%3Cstop offset='.4' stop-color='%23ff8b16'/%3E%3Cstop offset='.5' stop-color='%23ff672a'/%3E%3Cstop offset='.6' stop-color='%23ff3647'/%3E%3Cstop offset='.7' stop-color='%23e31587'/%3E%3C/radialGradient%3E%3CradialGradient id='ff-j' cx='235' cy='98.1' r='457.1' gradientUnits='userSpaceOnUse'%3E%3Cstop offset='.1' stop-color='%23fff44f'/%3E%3Cstop offset='.5' stop-color='%23ff980e'/%3E%3Cstop offset='.6' stop-color='%23ff5634'/%3E%3Cstop offset='.7' stop-color='%23ff3647'/%3E%3Cstop offset='.9' stop-color='%23e31587'/%3E%3C/radialGradient%3E%3CradialGradient id='ff-k' cx='355.7' cy='124.9' r='500.3' gradientUnits='userSpaceOnUse'%3E%3Cstop offset='.1' stop-color='%23fff44f'/%3E%3Cstop offset='.2' stop-color='%23ffe141'/%3E%3Cstop offset='.5' stop-color='%23ffaf1e'/%3E%3Cstop offset='.6' stop-color='%23ff980e'/%3E%3C/radialGradient%3E%3ClinearGradient id='ff-a' x1='446.9' y1='76.8' x2='47.9' y2='461.8' gradientUnits='userSpaceOnUse'%3E%3Cstop offset='.1' stop-color='%23fff44f'/%3E%3Cstop offset='.1' stop-color='%23ffe847'/%3E%3Cstop offset='.2' stop-color='%23ffc830'/%3E%3Cstop offset='.4' stop-color='%23ff980e'/%3E%3Cstop offset='.4' stop-color='%23ff8b16'/%3E%3Cstop offset='.5' stop-color='%23ff672a'/%3E%3Cstop offset='.5' stop-color='%23ff3647'/%3E%3Cstop offset='.7' stop-color='%23e31587'/%3E%3C/linearGradient%3E%3ClinearGradient id='ff-l' x1='442.1' y1='74.8' x2='102.6' y2='414.3' gradientUnits='userSpaceOnUse'%3E%3Cstop offset='.2' stop-color='%23fff44f' stop-opacity='.8'/%3E%3Cstop offset='.3' stop-color='%23fff44f' stop-opacity='.6'/%3E%3Cstop offset='.5' stop-color='%23fff44f' stop-opacity='.2'/%3E%3Cstop offset='.6' stop-color='%23fff44f' stop-opacity='0'/%3E%3C/linearGradient%3E%3C/defs%3E%3Cpath d='M479 166c-11-25-32-52-49-60a249 249 0 0 1 25 73c-27-68-73-95-111-155a255 255 0 0 1-8-14 44 44 0 0 1-4-9 1 1 0 0 0 0-1 1 1 0 0 0-1 0c-60 35-81 101-83 134a120 120 0 0 0-66 25 71 71 0 0 0-6-5 111 111 0 0 1-1-58c-25 11-44 29-58 44-9-12-9-52-8-60l-8 4a175 175 0 0 0-24 21 210 210 0 0 0-22 26 203 203 0 0 0-32 73l-1 2-2 15a229 229 0 0 0-4 34v1a240 240 0 0 0 477 40l1-9c5-41 0-84-15-121zM202 355l3 1-3-1zm55-145zm198-31z' fill='url(%23ff-a)'/%3E%3Cpath d='M479 166c-11-25-32-52-49-60 14 26 22 53 25 72v1a207 207 0 0 1-206 279c-113-3-212-87-231-197-3-17 0-26 2-40-2 11-3 14-4 34v1a240 240 0 0 0 477 40l1-9c5-41 0-84-15-121z' fill='url(%23ff-b)'/%3E%3Cpath d='M479 166c-11-25-32-52-49-60 14 26 22 53 25 72v1a207 207 0 0 1-206 279c-113-3-212-87-231-197-3-17 0-26 2-40-2 11-3 14-4 34v1a240 240 0 0 0 477 40l1-9c5-41 0-84-15-121z' fill='url(%23ff-c)'/%3E%3Cpath d='m362 195 1 1a130 130 0 0 0-22-29C266 92 322 5 331 0c-60 35-81 101-83 134l9-1c45 0 84 25 105 62z' fill='url(%23ff-d)'/%3E%3Cpath d='M257 210c-1 6-22 26-29 26-68 0-80 41-80 41 3 35 28 64 57 79l4 2 7 3a107 107 0 0 0 31 6c120 6 143-143 57-186 22-4 45 5 58 14-21-37-60-62-105-62l-9 1a120 120 0 0 0-66 25l17 16c16 16 58 33 58 35z' fill='url(%23ff-e)'/%3E%3Cpath d='M257 210c-1 6-22 26-29 26-68 0-80 41-80 41 3 35 28 64 57 79l4 2 7 3a107 107 0 0 0 31 6c120 6 143-143 57-186 22-4 45 5 58 14-21-37-60-62-105-62l-9 1a120 120 0 0 0-66 25l17 16c16 16 58 33 58 35z' fill='url(%23ff-f)'/%3E%3Cpath d='m171 151 5 3a111 111 0 0 1-1-58c-25 11-44 29-58 44 1 0 36 0 54 11z' fill='url(%23ff-g)'/%3E%3Cpath d='M18 261a242 242 0 0 0 231 197 207 207 0 0 0 206-279c8 56-20 110-64 146-86 71-169 43-186 31l-3-1c-50-24-71-70-67-110-42 0-57-35-57-35s38-28 89-4c46 22 90 4 90 4 0-2-42-19-58-35l-17-16a71 71 0 0 0-6-5l-5-3c-18-11-52-11-54-11-9-12-9-51-8-60l-8 4a175 175 0 0 0-24 21 210 210 0 0 0-22 26 203 203 0 0 0-32 73c0 1-9 38-5 57z' fill='url(%23ff-h)'/%3E%3Cpath d='M341 167a130 130 0 0 1 22 29 46 46 0 0 1 4 3c55 50 26 121 24 126 44-36 72-90 64-146-27-68-73-95-111-155a255 255 0 0 1-8-14 44 44 0 0 1-4-9 1 1 0 0 0 0-1 1 1 0 0 0-1 0c-9 5-65 92 10 167z' fill='url(%23ff-i)'/%3E%3Cpath d='M367 199a46 46 0 0 0-4-3l-1-1c-13-9-36-18-58-15 86 44 63 193-57 187a107 107 0 0 1-31-6 131 131 0 0 1-11-5c17 12 99 39 186-31 2-5 31-76-24-126z' fill='url(%23ff-j)'/%3E%3Cpath d='M148 277s12-41 80-41c7 0 28-20 29-26s-44 18-90-4c-51-24-89 4-89 4s15 35 57 35c-4 40 16 85 67 110l3 1c-29-15-54-44-57-79z' fill='url(%23ff-k)'/%3E%3Cpath d='M479 166c-11-25-32-52-49-60a249 249 0 0 1 25 73c-27-68-73-95-111-155a255 255 0 0 1-8-14 44 44 0 0 1-4-9 1 1 0 0 0 0-1 1 1 0 0 0-1 0c-60 35-81 101-83 134l9-1c45 0 84 25 105 62-13-9-36-18-58-14 86 43 63 192-57 186a107 107 0 0 1-31-6 131 131 0 0 1-11-5l-3-1 3 1c-29-15-54-44-57-79 0 0 12-41 80-41 7 0 28-20 29-26 0-2-42-19-58-35l-17-16a71 71 0 0 0-6-5 111 111 0 0 1-1-58c-25 11-44 29-58 44-9-12-9-52-8-60l-8 4a175 175 0 0 0-24 21 210 210 0 0 0-22 26 203 203 0 0 0-32 73l-1 2-2 15a279 279 0 0 0-4 34v1a240 240 0 0 0 477 40l1-9c5-41 0-84-15-121zm-24 13z' fill='url(%23ff-l)'/%3E%3C/svg%3E)
- ![Safari: 11.1.](data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' width='24' height='24' viewBox='195 190 135 135'%3E%3Cdefs%3E%3ClinearGradient id='s-a' x1='132.6' x2='134.4' y1='111.7' y2='-105.3' xlink:href='%23s-b'%3E%3Cstop offset='0' stop-color='%23d2d2d2' /%3E%3Cstop offset='.5' stop-color='%23f2f2f2' /%3E%3Cstop offset='1' stop-color='%23fff' /%3E%3C/linearGradient%3E%3ClinearGradient id='s-b' gradientUnits='userSpaceOnUse' /%3E%3ClinearGradient id='s-c' x1='65.4' x2='67.4' y1='115.7' y2='17.1' xlink:href='%23s-b'%3E%3Cstop offset='0' stop-color='%23005ad5' /%3E%3Cstop offset='.2' stop-color='%230875f0' /%3E%3Cstop offset='.3' stop-color='%23218cee' /%3E%3Cstop offset='.6' stop-color='%2327a5f3' /%3E%3Cstop offset='.8' stop-color='%2325aaf2' /%3E%3Cstop offset='1' stop-color='%2321aaef' /%3E%3C/linearGradient%3E%3ClinearGradient id='s-d' x1='158.7' x2='176.3' y1='96.7' y2='79.5' xlink:href='%23s-b'%3E%3Cstop offset='0' stop-color='%23c72e24' /%3E%3Cstop offset='1' stop-color='%23fd3b2f' /%3E%3C/linearGradient%3E%3CradialGradient id='s-i' cx='-69.9' cy='69.3' r='54' gradientTransform='matrix(.9 -.01 .04 2.72 -9 -120)' xlink:href='%23s-b'%3E%3Cstop offset='0' stop-color='%2324a5f3' stop-opacity='0' /%3E%3Cstop offset='1' stop-color='%231e8ceb' /%3E%3C/radialGradient%3E%3CradialGradient id='s-j' cx='109.3' cy='13.8' r='93.1' gradientTransform='matrix(-.02 1.1 -1.04 -.02 137 -115)' xlink:href='%23s-b'%3E%3Cstop offset='0' stop-opacity='0' /%3E%3Cstop offset='1' stop-color='%235488d6' stop-opacity='0' /%3E%3Cstop offset='1' stop-color='%235d96eb' /%3E%3C/radialGradient%3E%3C/defs%3E%3Crect width='220' height='220' x='22' y='-107' fill='url(%23s-a)' ry='49' transform='matrix(.57 0 0 .57 187 256)' /%3E%3Cg transform='translate(194 190)'%3E%3Ccircle cx='67.8' cy='67.7' fill='url(%23s-c)' paint-order='stroke fill markers' r='54' /%3E%3Ccircle cx='-69.9' cy='69.3' fill='url(%23s-i)' transform='translate(138 -2)' r='54' /%3E%3C/g%3E%3Cellipse cx='120' cy='14.2' fill='url(%23s-j)' rx='93.1' ry='93.7' transform='matrix(.58 0 0 .58 192 250)' /%3E%3Cg transform='matrix(.58 0 0 .57 197 182)'%3E%3Cpath fill='%23cac7c8' d='M46 192h1l72-48-7-9-66 57Z' /%3E%3Cpath fill='%23fbfffc' d='M46 191v1l66-57-7-9-59 65Z' /%3E%3Cpath fill='url(%23s-d)' d='m119 144-7-9 66-57-59 66Z' /%3E%3Cpath fill='%23fb645c' d='m105 126 7 9 66-57-1-1-72 49Z' /%3E%3C/g%3E%3Cpath stroke='%23fff' stroke-linecap='round' stroke-miterlimit='1' stroke-width='1.3' d='m287 278 3-2m-12-17 8-2m-8-3h4m-4-13 8 2m-8 3h4m-1-13 7 3m-4-11 7 4m-2-11 6 6m0-12 6 7m1-11 4 6m4-10 3 7m5-9 2 7m15-7-1 7m10-5-3 7m11-4-4 7m11-2-5 6m16 7-7 4m10 4-7 3m10 6-8 1m8 16-8-2m5 10-7-3m4 11-7-4m2 11-6-5m0 11-5-6m-2 11-4-7m-4 11-3-8m-6 10-1-8m-16 8 2-8m-10 5 3-7m-11 4 4-7m-11 2 5-6m-8 3 3-3m4 8 2-3m5 8 2-4m6 7 1-4m8 5v-4m8 4v-4m9 3-1-4m9 1-2-4m9 0-2-4m9-2-3-3m8-4-3-2m8-5-4-2m7-6-4-1m5-8h-4m4-8h-4m3-9-4 1m1-9-4 2m-1-9-3 2m-2-9-3 3m-4-8-2 3m-5-8-2 4m-6-6-1 3m-8-5v4m-8-4v4m-9-2 1 3m-9 0 2 3m-9 1 2 3m-9 2 3 3m-8 4 3 2m-8 5 4 2m-7 6 4 1m-4 25 4-1m-2 5 7-3m-6 7 4-2m-2 6 7-4m-13-21h8m41-41v-8m0 99v-8m49-42h-8' transform='translate(-65 8)' /%3E%3C/svg%3E)

[Source](https://developer.mozilla.org/docs/Web/API/PaymentRequest)

<br />

Checkout flow with platform-specific Google Pay app that uses Web Payments.

Compared to using just Android Intents, Web Payments allow better integration
with the browser, security, and user experience:

- The payment app is launched as a modal, in context of the merchant website.
- Implementation is supplemental to your existing payment app, enabling you to take advantage of your user base.
- The payment app's signature is checked to prevent [sideloading](https://en.wikipedia.org/wiki/Sideloading).
- Payment apps can support multiple payment methods.
- Any payment method, such as cryptocurrency, bank transfers, and more, can be integrated. Payment apps on Android devices can even integrate methods that require access to the hardware chip on the device.

> [!NOTE]
> **Note:** To understand how merchants integrate with payment apps, check out [Web Payments overview](https://web.dev/articles/web-payments-overview).

It takes four steps to implement Web Payments in an Android payment app:

1. Let merchants discover your payment app.
2. Let a merchant know if a customer has an enrolled instrument (such as credit card) that is ready to pay.
3. Let a customer make payment.
4. Verify the caller's signing certificate.

To see Web Payments in action, check out the
[android-web-payment](https://github.com/GoogleChromeLabs/android-web-payment/)
demo.

## Step 1: Let merchants discover your payment app

Set the `related_applications` property in the web app manifest according to the
instructions in [Setting up a payment method](https://web.dev/articles/setting-up-a-payment-method).
Chrome uses it to verify your app's package name, minimum version, and signing
certificate:

    {
      "prefer_related_applications": true,
      "related_applications": [{
        "platform": "play",
        "id": "com.example.android.samplepay",
        "min_version": "1",
        "fingerprints": [{
          "type": "sha256_cert",
          "value": "4C:FC:14:C6:97:DE:66:4E:66:97:50:C0:24:CE:5F:27:00:92:EE:F3:7F:18:B3:DA:77:66:84:CD:9D:E9:D2:CB"
        }]
      }]
    }

> [!CAUTION]
> **Caution:** When Chrome launches your Android payment app, it ignores the `serviceworker` and `payment` fields in the web app manifest. Declare supported delegations in your app's `AndroidManifest.xml` as well, as described in [Provide shipping and contact information from an Android payment app](https://web.dev/articles/android-payment-apps-delegation).

In order for a merchant to use your payment app, they need to use the [Payment
Request API](https://developer.mozilla.org/docs/Web/API/Payment_Request_API) and
specify the payment method you support using the [payment method
identifier](https://www.w3.org/TR/payment-method-id/).

If you have a payment method identifier that is unique to your payment app, you
can set up your own [payment method
manifest](https://w3c.github.io/payment-method-manifest/) so browsers can
discover your app.

## Step 2: Let a merchant know if a customer has an enrolled instrument that is ready to pay

The merchant can call `hasEnrolledInstrument()` to [query whether the customer
is able to make a payment](https://developer.mozilla.org/docs/Web/API/PaymentRequest/hasEnrolledInstrument). You can
implement `IS_READY_TO_PAY` as an Android service to answer this query.

### `AndroidManifest.xml`

Declare your service with an intent filter with the action
`org.chromium.intent.action.IS_READY_TO_PAY`.

    <service
      android:name=".SampleIsReadyToPayService"
      android:exported="true">
      <intent-filter>
        <action android:name="org.chromium.intent.action.IS_READY_TO_PAY" />
      </intent-filter>
    </service>

The `IS_READY_TO_PAY` service is optional. If there's no such intent handler in
the payment app, then the web browser assumes that the app can always make
payments.

### AIDL

The API for the `IS_READY_TO_PAY` service is defined in AIDL. Create two AIDL
files with the following content:

org/chromium/IsReadyToPayServiceCallback.aidl

    package org.chromium;

    interface IsReadyToPayServiceCallback {
        oneway void handleIsReadyToPay(boolean isReadyToPay);
    }

org/chromium/IsReadyToPayService.aidl

    package org.chromium;

    import org.chromium.IsReadyToPayServiceCallback;

    interface IsReadyToPayService {
        oneway void isReadyToPay(IsReadyToPayServiceCallback callback, in Bundle parameters);
    }

### Implementing `IsReadyToPayService`

The simplest implementation of `IsReadyToPayService` is shown in the following
example:

### Kotlin

    class SampleIsReadyToPayService : Service() {
        private val binder = object : IsReadyToPayService.Stub() {
            override fun isReadyToPay(callback: IsReadyToPayServiceCallback?, parameters: Bundle?) {
                callback?.handleIsReadyToPay(true)
            }
        }

        override fun onBind(intent: Intent?): IBinder? {
            return binder
        }
    }

### Java

    import org.chromium.IsReadyToPayService;

    public class SampleIsReadyToPayService extends Service {
        private final IsReadyToPayService.Stub mBinder =
            new IsReadyToPayService.Stub() {
                @Override
                public void isReadyToPay(IsReadyToPayServiceCallback callback, Bundle parameters) {
                    if (callback != null) {
                        callback.handleIsReadyToPay(true);
                    }
                }
            };

        @Override
        public IBinder onBind(Intent intent) {
            return mBinder;
        }
    }

### Response

The service can send its response using the `handleIsReadyToPay(Boolean)` method.

### Kotlin

    callback?.handleIsReadyToPay(true)

### Java

    if (callback != null) {
        callback.handleIsReadyToPay(true);
    }

### Permission

You can use `Binder.getCallingUid()` to check who the caller is. Note that you
have to do this in the `isReadyToPay` method, not in the `onBind` method,
because Android OS can cache and reuse the service connection, which does not
trigger the `onBind()` method.

### Kotlin

    override fun isReadyToPay(callback: IsReadyToPayServiceCallback?, parameters: Bundle?) {
        try {
            val untrustedPackageName = parameters?.getString("packageName")
            val actualPackageNames = packageManager.getPackagesForUid(Binder.getCallingUid())
            // ...

### Java

    @Override
    public void isReadyToPay(IsReadyToPayServiceCallback callback, Bundle parameters) {
        try {
            String untrustedPackageName = parameters != null
                    ? parameters.getString("packageName")
                    : null;
            String[] actualPackageNames = packageManager.getPackagesForUid(Binder.getCallingUid());
            // ...

Always check input parameters for `null` when receiving
Inter-Process Communication (IPC) calls. This is
particularly important because different versions or
forks of the Android OS can behave in unexpected ways
and lead to errors if not handled.

While `packageManager.getPackagesForUid()` typically
returns a single element, your code must handle the
uncommon scenario where a caller utilizes multiple
package names. This ensures your application remains
robust.

See [Verify the caller's signing certificate](https://web.dev/articles/android-payment-apps-developers-guide#step_4_verify_the_callers_signing_certificate) about how
to verify that the calling package has the right signature.

### Parameters

The `parameters` Bundle was added in Chrome 139. It should always be checked
against `null`.

The following parameters are passed to the service in the `parameters` Bundle:

- `packageName`
- `methodNames`
- `methodData`
- `topLevelOrigin`
- `paymentRequestOrigin`
- `topLevelCertificateChain`

The `packageName` was added in Chrome 138. You must verify
this parameter against `Binder.getCallingUid()` before
using its value. This verification is essential because
the `parameters` bundle is under full control of the caller,
while `Binder.getCallingUid()` is controlled by the Android OS.

The `topLevelCertificateChain` is `null` in WebView and on non-https websites
that are typically used for local testing, such as `http://localhost`.

## Step 3: Let a customer make payment

The merchant calls `show()` to [launch the payment
app](https://developer.mozilla.org/docs/Web/API/PaymentRequest/show)
so the customer can make a payment. The payment app is invoked using an Android
intent `PAY` with transaction information in the intent parameters.

The payment app responds with `methodName` and `details`, which are payment app
specific and are opaque to the browser. The browser converts the `details`
string into a JavaScript dictionary for the merchant using JSON string
deserialization, but does not enforce any validity beyond that. The browser does
not modify the `details`; that parameter's value is passed directly to the
merchant.

### `AndroidManifest.xml`

The activity with the `PAY` intent filter should have a `<meta-data>` tag [that
identifies the default payment method identifier for the
app](https://web.dev/articles/setting-up-a-payment-method).

To support multiple payment methods, add a `<meta-data>` tag with a
`<string-array>` resource.

    <activity
      android:name=".PaymentActivity"
      android:theme="@style/Theme.SamplePay.Dialog">
      <intent-filter>
        <action android:name="org.chromium.intent.action.PAY" />
      </intent-filter>

      <meta-data
        android:name="org.chromium.default_payment_method_name"
        android:value="https://bobbucks.dev/pay" />
      <meta-data
        android:name="org.chromium.payment_method_names"
        android:resource="@array/chromium_payment_method_names" />
    </activity>

The `android:resource` must be a list of strings, each of which must be a valid,
absolute URL with an HTTPS scheme as shown here.

    <?xml version="1.0" encoding="utf-8"?>
    <resources>
        <string-array name="chromium_payment_method_names">
            <item>https://alicepay.com/put/optional/path/here</item>
            <item>https://charliepay.com/put/optional/path/here</item>
        </string-array>
    </resources>

### Parameters

The following parameters are passed to the activity as `Intent` extras:

- `methodNames`
- `methodData`
- `merchantName`
- `topLevelOrigin`
- `topLevelCertificateChain`
- `paymentRequestOrigin`
- `total`
- `modifiers`
- `paymentRequestId`
- `paymentOptions`
- `shippingOptions`

### Kotlin

    val extras: Bundle? = getIntent()?.extras

### Java

    Bundle extras = getIntent() != null ? getIntent().getExtras() : null;

#### methodNames

The names of the methods being used. The elements are the keys in the
`methodData` dictionary. These are the methods that the payment app supports.

### Kotlin

    val methodNames: List<String>? = extras.getStringArrayList("methodNames")

### Java

    List<String> methodNames = extras.getStringArrayList("methodNames");

#### `methodData`

A mapping from each of the `methodNames` to the
[`methodData`](https://w3c.github.io/payment-request/#declaring-multiple-ways-of-paying).

### Kotlin

    val methodData: Bundle? = extras.getBundle("methodData")

### Java

    Bundle methodData = extras.getBundle("methodData");

#### `merchantName`

The contents of the `<title>` HTML tag of the merchant's checkout page (the
browser's top-level browsing context).

### Kotlin

    val merchantName: String? = extras.getString("merchantName")

### Java

    String merchantName = extras.getString("merchantName");

#### `topLevelOrigin`

The merchant's origin without the scheme (The scheme-less origin of the
top-level browsing context). For example, `https://mystore.com/checkout` is
passed as `mystore.com`.

### Kotlin

    val topLevelOrigin: String? = extras.getString("topLevelOrigin")

### Java

    String topLevelOrigin = extras.getString("topLevelOrigin");

#### `topLevelCertificateChain`

The merchant's certificate chain (the certificate chain of the top-level
browsing context). The value is `null` for WebView, localhost, or a file on disk.
Each `Parcelable` is a Bundle with a `certificate` key and a byte array value.

### Kotlin

    val topLevelCertificateChain: Array<Parcelable>? =
            extras.getParcelableArray("topLevelCertificateChain")
    val list: List<ByteArray>? = topLevelCertificateChain?.mapNotNull { p ->
        (p as Bundle).getByteArray("certificate")
    }

### Java

    Parcelable[] topLevelCertificateChain =
            extras.getParcelableArray("topLevelCertificateChain");
    if (topLevelCertificateChain != null) {
        for (Parcelable p : topLevelCertificateChain) {
            if (p != null && p instanceof Bundle) {
                ((Bundle) p).getByteArray("certificate");
            }
        }
    }

#### `paymentRequestOrigin`

The scheme-less origin of the iframe browsing context that invoked the `new
PaymentRequest(methodData, details, options)` constructor in JavaScript. If the
constructor was invoked from the top-level context, then the value of this
parameter equals the value of `topLevelOrigin` parameter.

### Kotlin

    val paymentRequestOrigin: String? = extras.getString("paymentRequestOrigin")

### Java

    String paymentRequestOrigin = extras.getString("paymentRequestOrigin");

#### `total`

The JSON string representing the total amount of the transaction.

### Kotlin

    val total: String? = extras.getString("total")

### Java

    String total = extras.getString("total");

Here's an example content of the string:

    {"currency":"USD","value":"25.00"}

#### `modifiers`

The output of `JSON.stringify(details.modifiers)`, where `details.modifiers`
contain only `supportedMethods`, `data`, and `total`.

#### `paymentRequestId`

The `PaymentRequest.id` field that "push-payment" apps should associate with the
transaction state. Merchant websites will use this field to query the
"push-payment" apps for the state of transaction out of band.

### Kotlin

    val paymentRequestId: String? = extras.getString("paymentRequestId")

### Java

    String paymentRequestId = extras.getString("paymentRequestId");

### Response

The activity can send its response back through `setResult` with `RESULT_OK`.

### Kotlin

    setResult(Activity.RESULT_OK, Intent().apply {
        putExtra("methodName", "https://bobbucks.dev/pay")
        putExtra("details", "{\"token\": \"put-some-data-here\"}")
    })
    finish()

### Java

    Intent result = new Intent();
    Bundle extras = new Bundle();
    extras.putString("methodName", "https://bobbucks.dev/pay");
    extras.putString("details", "{\"token\": \"put-some-data-here\"}");
    result.putExtras(extras);
    setResult(Activity.RESULT_OK, result);
    finish();

You must specify two parameters as Intent extras:

- `methodName`: The name of the method being used.
- `details`: JSON string containing information necessary for the merchant to complete the transaction. If success is `true`, then `details` must be constructed in such a way that `JSON.parse(details)` will succeed. If there is no data that needs to be returned, then this string can be `"{}"`, which the merchant website will receive as an empty JavaScript dictionary.

You can pass `RESULT_CANCELED` if the user cancels the transaction in the payment
app. Doing so will cause `request.show()` to reject with an `AbortError` on the
merchant website, indicating user cancellation.

### Kotlin

    setResult(Activity.RESULT_CANCELED)
    finish()

### Java

    setResult(Activity.RESULT_CANCELED);
    finish();

From Chrome 149, the following result values are supported:

### Kotlin

    Activity.RESULT_CANCELED // 0 (0x00000000)
    Activity.RESULT_OK // -1 (0xffffffff)
    const val INTERNAL_PAYMENT_APP_ERROR = Activity.RESULT_FIRST_USER // 1 (0x00000001)

### Java

    Activity.RESULT_CANCELED // 0 (0x00000000)
    Activity.RESULT_OK // -1 (0xffffffff)
    static final int INTERNAL_PAYMENT_APP_ERROR = Activity.RESULT_FIRST_USER; // 1 (0x00000001)

If the payment app fails due to an internal error, you
can indicate it by passing `Activity.RESULT_FIRST_USER` as result code.

If `INTERNAL_PAYMENT_APP_ERROR` is returned, `request.show()` will reject with an
`OperationError` on the merchant website, indicating an error in the payment app.

This distinction between `RESULT_CANCELED` (0) for user cancellation, which
causes `AbortError`, and `INTERNAL_PAYMENT_APP_ERROR` (1) for an internal app
error, which causes `OperationError`, allows merchants to build better user flows.

### Kotlin

    setResult(Activity.RESULT_FIRST_USER)
    finish()

### Java

    setResult(Activity.RESULT_FIRST_USER);
    finish();

If the activity result of a payment response received from the invoked payment
app is set to `RESULT_OK`, then Chrome will check for non-empty `methodName` and
`details` in its extras. If the validation fails Chrome will return a rejected
promise from `request.show()` with one of the following developer facing error
messages:

    'Payment app returned invalid response. Missing field "details".'
    'Payment app returned invalid response. Missing field "methodName".'

### Permission

The activity can check the caller with its `getCallingPackage()` method.

### Kotlin

    val caller: String? = callingPackage

### Java

    String caller = getCallingPackage();

The final step is to verify the caller's signing certificate to confirm that the
calling package has the right signature.

## Step 4: Verify the caller's signing certificate

You can check the caller's package name with `Binder.getCallingUid()` in
`IS_READY_TO_PAY`, and with `Activity.getCallingPackage()` in `PAY`. In order to
actually verify that the caller is the browser you have in mind, you should
check its signing certificate and make sure that it matches with the correct
value.

If you're targeting API level 28 and later and are integrating with a browser
that has a single signing certificate, you can use
`PackageManager.hasSigningCertificate()`.

### Kotlin

    val packageName: String = ... // The caller's package name
    val certificate: ByteArray = ... // The correct signing certificate
    val verified = packageManager.hasSigningCertificate(
            callingPackage,
            certificate,
            PackageManager.CERT_INPUT_SHA256
    )

### Java

    String packageName = ... // The caller's package name
    byte[] certificate = ... // The correct signing certificate
    boolean verified = packageManager.hasSigningCertificate(
            callingPackage,
            certificate,
            PackageManager.CERT_INPUT_SHA256);

`PackageManager.hasSigningCertificate()` is preferred for single certificate
browsers, because it correctly handles certificate rotation. (Chrome has a
single signing certificate.) Apps that have multiple signing certificates cannot
rotate them.

If you need to support API levels 27 and earlier, or if you need to handle
browsers with multiple signing certificates, you can use
`PackageManager.GET_SIGNATURES`.

### Kotlin

    val packageName: String = ... // The caller's package name
    val expected: Set<String> = ... // The correct set of signing certificates

    val packageInfo = packageManager.getPackageInfo(packageName, PackageManager.GET_SIGNATURES)
    val sha256 = MessageDigest.getInstance("SHA-256")
    val actual = packageInfo.signatures.map {
        SerializeByteArrayToString(sha256.digest(it.toByteArray()))
    }
    val verified = actual.equals(expected)

### Java

    String packageName  = ... // The caller's package name
    Set<String> expected = ... // The correct set of signing certificates

    PackageInfo packageInfo =
            packageManager.getPackageInfo(packageName, PackageManager.GET_SIGNATURES);
    MessageDigest sha256 = MessageDigest.getInstance("SHA-256");
    Set<String> actual = new HashSet<>();
    for (Signature it : packageInfo.signatures) {
        actual.add(SerializeByteArrayToString(sha256.digest(it.toByteArray())));
    }
    boolean verified = actual.equals(expected);

## Debug

Use the following command to observe errors or informational messages:

    adb logcat | grep -i pay