diff --git a/resources/celerity/map.php b/resources/celerity/map.php index eb7862b810..2638acd08c 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -1,2151 +1,2146 @@ array( 'core.pkg.css' => 'e8a5c5fa', 'core.pkg.js' => 'cbdbd552', 'darkconsole.pkg.js' => 'df001cab', 'differential.pkg.css' => '0f9c3082', 'differential.pkg.js' => '73337d1d', 'diffusion.pkg.css' => '591664fa', 'diffusion.pkg.js' => 'bfc0737b', 'maniphest.pkg.css' => 'f5d89daf', 'maniphest.pkg.js' => 'df4aa49f', 'rsrc/css/aphront/aphront-bars.css' => '231ac33c', 'rsrc/css/aphront/context-bar.css' => '1c3b0529', 'rsrc/css/aphront/dark-console.css' => '6378ef3d', 'rsrc/css/aphront/dialog-view.css' => '4dbbe3bb', 'rsrc/css/aphront/error-view.css' => '3462dbee', 'rsrc/css/aphront/lightbox-attachment.css' => '7acac05d', 'rsrc/css/aphront/list-filter-view.css' => '2ae43867', 'rsrc/css/aphront/multi-column.css' => '1b95ab2e', 'rsrc/css/aphront/notification.css' => 'ef2c9b34', 'rsrc/css/aphront/pager-view.css' => '2e3539af', 'rsrc/css/aphront/panel-view.css' => '5846dfa2', 'rsrc/css/aphront/phabricator-nav-view.css' => '9283c2df', 'rsrc/css/aphront/table-view.css' => 'b22b7216', 'rsrc/css/aphront/tokenizer.css' => '82ce2142', 'rsrc/css/aphront/tooltip.css' => '9c90229d', 'rsrc/css/aphront/transaction.css' => '5d0cae25', 'rsrc/css/aphront/two-column.css' => '16ab3ad2', 'rsrc/css/aphront/typeahead.css' => 'a989b5b3', 'rsrc/css/application/auth/auth.css' => '1e655982', 'rsrc/css/application/base/main-menu-view.css' => 'aceca0e9', 'rsrc/css/application/base/notification-menu.css' => '6aa0a74b', 'rsrc/css/application/base/phabricator-application-launch-view.css' => '5d71008f', 'rsrc/css/application/base/standard-page-view.css' => '3f5b9311', 'rsrc/css/application/chatlog/chatlog.css' => '852140ff', 'rsrc/css/application/config/config-options.css' => '7fedf08b', 'rsrc/css/application/config/config-template.css' => '25d446d6', 'rsrc/css/application/config/config-welcome.css' => 'b0d16200', 'rsrc/css/application/config/setup-issue.css' => '8f852bc0', 'rsrc/css/application/conpherence/menu.css' => 'e1e0fdf1', 'rsrc/css/application/conpherence/message-pane.css' => '11a393ca', 'rsrc/css/application/conpherence/notification.css' => '04a6e10a', 'rsrc/css/application/conpherence/update.css' => '1099a660', 'rsrc/css/application/conpherence/widget-pane.css' => 'bf275a6c', 'rsrc/css/application/contentsource/content-source-view.css' => '4b8b05d4', 'rsrc/css/application/countdown/timer.css' => '86b7b0a0', 'rsrc/css/application/dashboard/dashboard.css' => 'a2bfdcbf', 'rsrc/css/application/diff/inline-comment-summary.css' => '8cfd34e8', 'rsrc/css/application/differential/add-comment.css' => 'c478bcaa', 'rsrc/css/application/differential/changeset-view.css' => 'b2b71e76', 'rsrc/css/application/differential/core.css' => '7ac3cabc', 'rsrc/css/application/differential/results-table.css' => '181aa9d9', 'rsrc/css/application/differential/revision-comment.css' => '48186045', 'rsrc/css/application/differential/revision-history.css' => '0e8eb855', 'rsrc/css/application/differential/revision-list.css' => 'f3c47d33', 'rsrc/css/application/differential/table-of-contents.css' => '6bf8e1d2', 'rsrc/css/application/diffusion/diffusion-icons.css' => '9c5828da', 'rsrc/css/application/diffusion/diffusion-source.css' => '66fdf661', 'rsrc/css/application/feed/feed.css' => '7bfc6f12', 'rsrc/css/application/files/global-drag-and-drop.css' => '697324ad', 'rsrc/css/application/flag/flag.css' => '5337623f', 'rsrc/css/application/harbormaster/harbormaster.css' => '49d64eb4', 'rsrc/css/application/herald/herald-test.css' => '778b008e', 'rsrc/css/application/herald/herald.css' => 'c544dd1c', 'rsrc/css/application/maniphest/batch-editor.css' => '8f380ebc', 'rsrc/css/application/maniphest/report.css' => '6fc16517', 'rsrc/css/application/maniphest/task-edit.css' => '8e23031b', 'rsrc/css/application/maniphest/task-summary.css' => '00c3be7a', 'rsrc/css/application/objectselector/object-selector.css' => '029a133d', 'rsrc/css/application/owners/owners-path-editor.css' => '2f00933b', 'rsrc/css/application/paste/paste.css' => 'aa1767d1', 'rsrc/css/application/people/people-profile.css' => '5402f7a5', 'rsrc/css/application/phame/phame.css' => '19ecc703', 'rsrc/css/application/pholio/pholio-edit.css' => '3ad9d1ee', 'rsrc/css/application/pholio/pholio-inline-comments.css' => '8e545e49', 'rsrc/css/application/pholio/pholio.css' => '47dffb9c', 'rsrc/css/application/phortune/phortune-credit-card-form.css' => 'b25b4beb', 'rsrc/css/application/phortune/phortune.css' => '9149f103', 'rsrc/css/application/phrequent/phrequent.css' => 'ffc185ad', 'rsrc/css/application/phriction/phriction-document-css.css' => '7d7f0071', 'rsrc/css/application/policy/policy-edit.css' => '05cca26a', 'rsrc/css/application/policy/policy-transaction-detail.css' => '82100a43', 'rsrc/css/application/policy/policy.css' => '957ea14c', 'rsrc/css/application/ponder/comments.css' => '6cdccea7', 'rsrc/css/application/ponder/feed.css' => 'e62615b6', 'rsrc/css/application/ponder/post.css' => 'ebab8a70', 'rsrc/css/application/ponder/vote.css' => '8ed6ed8b', 'rsrc/css/application/profile/profile-view.css' => '28f433ef', 'rsrc/css/application/projects/project-icon.css' => 'c2ecb7f1', 'rsrc/css/application/releeph/releeph-core.css' => '9b3c5733', 'rsrc/css/application/releeph/releeph-preview-branch.css' => 'b7a6f4a5', 'rsrc/css/application/releeph/releeph-request-differential-create-dialog.css' => '8d8b92cd', 'rsrc/css/application/releeph/releeph-request-typeahead.css' => '667a48ae', 'rsrc/css/application/search/search-results.css' => 'f240504c', 'rsrc/css/application/slowvote/slowvote.css' => '266df6a1', 'rsrc/css/application/tokens/tokens.css' => '3d0f239e', 'rsrc/css/application/uiexample/example.css' => '528b19de', 'rsrc/css/core/core.css' => '40151074', 'rsrc/css/core/remarkup.css' => '45313445', 'rsrc/css/core/syntax.css' => '863f3cd8', 'rsrc/css/core/z-index.css' => '44e1d311', 'rsrc/css/diviner/diviner-shared.css' => '38813222', 'rsrc/css/font/font-awesome.css' => '73d075c3', 'rsrc/css/font/font-source-sans-pro.css' => '91d53463', 'rsrc/css/font/phui-font-icon-base.css' => 'eb84f033', 'rsrc/css/layout/phabricator-action-header-view.css' => '83e2cc86', 'rsrc/css/layout/phabricator-crumbs-view.css' => 'a49339de', 'rsrc/css/layout/phabricator-filetree-view.css' => 'fccf9f82', 'rsrc/css/layout/phabricator-hovercard-view.css' => '893f4783', 'rsrc/css/layout/phabricator-side-menu-view.css' => 'a2ccd7bd', 'rsrc/css/layout/phabricator-source-code-view.css' => '7d346aa4', 'rsrc/css/phui/calendar/phui-calendar-day.css' => 'de035c8a', 'rsrc/css/phui/calendar/phui-calendar-list.css' => 'c1d0ca59', 'rsrc/css/phui/calendar/phui-calendar-month.css' => 'a92e47d2', 'rsrc/css/phui/calendar/phui-calendar.css' => '5e1ad989', 'rsrc/css/phui/phui-action-list.css' => '9ee9910a', 'rsrc/css/phui/phui-box.css' => '7b3a2eed', 'rsrc/css/phui/phui-button.css' => 'c7412aa1', 'rsrc/css/phui/phui-document.css' => 'a5615198', 'rsrc/css/phui/phui-feed-story.css' => '55dc7732', 'rsrc/css/phui/phui-fontkit.css' => 'fff25cfa', 'rsrc/css/phui/phui-form-view.css' => 'a2d72756', 'rsrc/css/phui/phui-form.css' => 'b78ec020', 'rsrc/css/phui/phui-header-view.css' => '39594ac0', 'rsrc/css/phui/phui-icon.css' => 'b4963a4f', 'rsrc/css/phui/phui-image-mask.css' => '5a8b09c8', 'rsrc/css/phui/phui-info-panel.css' => '27ea50a1', 'rsrc/css/phui/phui-list.css' => '43ed2d93', 'rsrc/css/phui/phui-object-box.css' => 'e9f7e938', 'rsrc/css/phui/phui-object-item-list-view.css' => 'e1e6425f', 'rsrc/css/phui/phui-pinboard-view.css' => '3dd4a269', 'rsrc/css/phui/phui-property-list-view.css' => '86f9df88', 'rsrc/css/phui/phui-remarkup-preview.css' => '19ad512b', 'rsrc/css/phui/phui-spacing.css' => '042804d6', 'rsrc/css/phui/phui-status.css' => '2f562399', 'rsrc/css/phui/phui-tag-view.css' => 'c4158073', 'rsrc/css/phui/phui-text.css' => '23e9b4b7', 'rsrc/css/phui/phui-timeline-view.css' => 'bbd990d0', 'rsrc/css/phui/phui-workboard-view.css' => '2bf82d00', 'rsrc/css/phui/phui-workpanel-view.css' => '198c7e6c', 'rsrc/css/sprite-apps-large.css' => '20ec0cc0', 'rsrc/css/sprite-apps.css' => 'd5baed0f', 'rsrc/css/sprite-conpherence.css' => '3b4a0487', 'rsrc/css/sprite-docs.css' => '5f65d0da', 'rsrc/css/sprite-gradient.css' => '4bdb98a7', 'rsrc/css/sprite-login.css' => 'cf08ac44', 'rsrc/css/sprite-main-header.css' => '92720ee2', 'rsrc/css/sprite-menu.css' => '28281e16', 'rsrc/css/sprite-payments.css' => 'cc085d44', 'rsrc/css/sprite-projects.css' => '7578fa56', 'rsrc/css/sprite-tokens.css' => '1706b943', 'rsrc/externals/font/fontawesome/fontawesome-webfont.eot' => '1cab0752', 'rsrc/externals/font/fontawesome/fontawesome-webfont.ttf' => '2ff84fd2', 'rsrc/externals/font/fontawesome/fontawesome-webfont.woff' => 'a119ee5e', 'rsrc/externals/font/sourcesans/SourceSansPro.woff' => '3614608c', 'rsrc/externals/font/sourcesans/SourceSansProBold.woff' => 'cbf46566', 'rsrc/externals/javelin/core/Event.js' => '85ea0626', 'rsrc/externals/javelin/core/Stratcom.js' => '8b0ad945', 'rsrc/externals/javelin/core/__tests__/event-stop-and-kill.js' => '717554e4', 'rsrc/externals/javelin/core/__tests__/install.js' => 'c432ee85', 'rsrc/externals/javelin/core/__tests__/stratcom.js' => 'da194d4b', 'rsrc/externals/javelin/core/__tests__/util.js' => 'd3b157a9', 'rsrc/externals/javelin/core/init.js' => 'b88ab49e', 'rsrc/externals/javelin/core/init_node.js' => 'd7dde471', 'rsrc/externals/javelin/core/install.js' => '1ffb3a9c', 'rsrc/externals/javelin/core/util.js' => 'e7995242', 'rsrc/externals/javelin/docs/Base.js' => '74676256', 'rsrc/externals/javelin/docs/onload.js' => 'e819c479', 'rsrc/externals/javelin/ext/fx/Color.js' => '7e41274a', 'rsrc/externals/javelin/ext/fx/FX.js' => '54b612ba', 'rsrc/externals/javelin/ext/reactor/core/DynVal.js' => 'f6555212', 'rsrc/externals/javelin/ext/reactor/core/Reactor.js' => '77b1cf6f', 'rsrc/externals/javelin/ext/reactor/core/ReactorNode.js' => 'b4c30592', 'rsrc/externals/javelin/ext/reactor/core/ReactorNodeCalmer.js' => '76f4ebed', 'rsrc/externals/javelin/ext/reactor/dom/RDOM.js' => 'b6d401d6', 'rsrc/externals/javelin/ext/view/HTMLView.js' => 'e5b406f9', 'rsrc/externals/javelin/ext/view/View.js' => '0f764c35', 'rsrc/externals/javelin/ext/view/ViewInterpreter.js' => '0c33c1a0', 'rsrc/externals/javelin/ext/view/ViewPlaceholder.js' => '2fa810fc', 'rsrc/externals/javelin/ext/view/ViewRenderer.js' => '6c2b09a2', 'rsrc/externals/javelin/ext/view/ViewVisitor.js' => 'efe49472', 'rsrc/externals/javelin/ext/view/__tests__/HTMLView.js' => 'f92d7bcb', 'rsrc/externals/javelin/ext/view/__tests__/View.js' => 'bda69c40', 'rsrc/externals/javelin/ext/view/__tests__/ViewInterpreter.js' => '7a94d6a5', 'rsrc/externals/javelin/ext/view/__tests__/ViewRenderer.js' => '5426001c', 'rsrc/externals/javelin/lib/Cookie.js' => '6b3dcf44', 'rsrc/externals/javelin/lib/DOM.js' => 'c4569c05', 'rsrc/externals/javelin/lib/History.js' => 'c60f4327', 'rsrc/externals/javelin/lib/JSON.js' => '69adf288', 'rsrc/externals/javelin/lib/Mask.js' => '8a41885b', 'rsrc/externals/javelin/lib/Request.js' => '97258e55', 'rsrc/externals/javelin/lib/Resource.js' => '0f81f8df', 'rsrc/externals/javelin/lib/Routable.js' => 'b3e7d692', 'rsrc/externals/javelin/lib/Router.js' => '29274e2b', 'rsrc/externals/javelin/lib/URI.js' => '6eff08aa', 'rsrc/externals/javelin/lib/Vector.js' => 'cc1bd0b0', 'rsrc/externals/javelin/lib/Workflow.js' => 'd149e002', 'rsrc/externals/javelin/lib/__tests__/Cookie.js' => '5ed109e8', 'rsrc/externals/javelin/lib/__tests__/DOM.js' => 'c984504b', 'rsrc/externals/javelin/lib/__tests__/JSON.js' => '2295d074', 'rsrc/externals/javelin/lib/__tests__/URI.js' => '003ed329', 'rsrc/externals/javelin/lib/__tests__/behavior.js' => '1ea62783', 'rsrc/externals/javelin/lib/behavior.js' => '61cbc29a', 'rsrc/externals/javelin/lib/control/tokenizer/Tokenizer.js' => '9f06389f', 'rsrc/externals/javelin/lib/control/typeahead/Typeahead.js' => 'e614d22b', 'rsrc/externals/javelin/lib/control/typeahead/normalizer/TypeaheadNormalizer.js' => '1c22377d', 'rsrc/externals/javelin/lib/control/typeahead/source/TypeaheadCompositeSource.js' => '503e17fd', 'rsrc/externals/javelin/lib/control/typeahead/source/TypeaheadOnDemandSource.js' => '8b3fd187', 'rsrc/externals/javelin/lib/control/typeahead/source/TypeaheadPreloadedSource.js' => '54f314a0', 'rsrc/externals/javelin/lib/control/typeahead/source/TypeaheadSource.js' => 'fcba4ecc', 'rsrc/externals/javelin/lib/control/typeahead/source/TypeaheadStaticSource.js' => '316b8fa1', 'rsrc/externals/raphael/g.raphael.js' => '40dde778', 'rsrc/externals/raphael/g.raphael.line.js' => '40da039e', 'rsrc/externals/raphael/raphael.js' => '51ee6b43', 'rsrc/image/BFCFDA.png' => 'd5ec91f4', 'rsrc/image/actions/edit.png' => '2fc41442', 'rsrc/image/apple-touch-icon.png' => '8458dda7', 'rsrc/image/avatar.png' => '3eb28cd9', 'rsrc/image/checker_dark.png' => 'd8e65881', 'rsrc/image/checker_light.png' => 'a0155918', 'rsrc/image/checker_lighter.png' => 'd5da91b6', 'rsrc/image/credit_cards.png' => '72b8ede8', 'rsrc/image/darkload.gif' => '1ffd3ec6', 'rsrc/image/divot.png' => '94dded62', 'rsrc/image/examples/hero.png' => '979a86ae', 'rsrc/image/grippy_texture.png' => 'aca81e2f', 'rsrc/image/icon/fatcow/arrow_branch.png' => '2537c01c', 'rsrc/image/icon/fatcow/arrow_merge.png' => '21b660e0', 'rsrc/image/icon/fatcow/bullet_black.png' => 'ff190031', 'rsrc/image/icon/fatcow/bullet_orange.png' => 'e273e5bb', 'rsrc/image/icon/fatcow/bullet_red.png' => 'c0b75434', 'rsrc/image/icon/fatcow/calendar_edit.png' => '24632275', 'rsrc/image/icon/fatcow/document_black.png' => '45fe1c60', 'rsrc/image/icon/fatcow/flag_blue.png' => 'a01abb1d', 'rsrc/image/icon/fatcow/flag_finish.png' => '67825cee', 'rsrc/image/icon/fatcow/flag_ghost.png' => '20ca8783', 'rsrc/image/icon/fatcow/flag_green.png' => '7e0eaa7a', 'rsrc/image/icon/fatcow/flag_orange.png' => '9e73df66', 'rsrc/image/icon/fatcow/flag_pink.png' => '7e92f3b2', 'rsrc/image/icon/fatcow/flag_purple.png' => 'cc517522', 'rsrc/image/icon/fatcow/flag_red.png' => '04ec726f', 'rsrc/image/icon/fatcow/flag_yellow.png' => '73946fd4', 'rsrc/image/icon/fatcow/folder.png' => '95a435af', 'rsrc/image/icon/fatcow/folder_go.png' => '001cbc94', 'rsrc/image/icon/fatcow/key_question.png' => '52a0c26a', 'rsrc/image/icon/fatcow/link.png' => '7afd4d5e', 'rsrc/image/icon/fatcow/page_white_edit.png' => '39a2eed8', 'rsrc/image/icon/fatcow/page_white_link.png' => 'a90023c7', 'rsrc/image/icon/fatcow/page_white_put.png' => '08c95a0c', 'rsrc/image/icon/fatcow/page_white_text.png' => '1e1f79c3', 'rsrc/image/icon/fatcow/source/conduit.png' => '4ea01d2f', 'rsrc/image/icon/fatcow/source/email.png' => '9bab3239', 'rsrc/image/icon/fatcow/source/fax.png' => '04195e68', 'rsrc/image/icon/fatcow/source/mobile.png' => 'f1321264', 'rsrc/image/icon/fatcow/source/tablet.png' => '49396799', 'rsrc/image/icon/fatcow/source/web.png' => '136ccb5d', 'rsrc/image/icon/fatcow/thumbnails/default.p100.png' => '7d490b01', 'rsrc/image/icon/fatcow/thumbnails/default160x120.png' => 'f2e8a2eb', 'rsrc/image/icon/fatcow/thumbnails/default280x210.png' => '43e8926a', 'rsrc/image/icon/fatcow/thumbnails/default60x45.png' => '0118abed', 'rsrc/image/icon/fatcow/thumbnails/image.p100.png' => 'da23cf97', 'rsrc/image/icon/fatcow/thumbnails/image160x120.png' => '79bb556a', 'rsrc/image/icon/fatcow/thumbnails/image280x210.png' => '91ae054a', 'rsrc/image/icon/fatcow/thumbnails/image60x45.png' => 'c5e1685e', 'rsrc/image/icon/fatcow/thumbnails/pdf.p100.png' => '87d5e065', 'rsrc/image/icon/fatcow/thumbnails/pdf160x120.png' => 'ac9edbf5', 'rsrc/image/icon/fatcow/thumbnails/pdf280x210.png' => '1c585653', 'rsrc/image/icon/fatcow/thumbnails/pdf60x45.png' => 'c0db4143', 'rsrc/image/icon/fatcow/thumbnails/zip.p100.png' => '6ea5aae4', 'rsrc/image/icon/fatcow/thumbnails/zip160x120.png' => '75f9cd0f', 'rsrc/image/icon/fatcow/thumbnails/zip280x210.png' => 'dfda5b8e', 'rsrc/image/icon/fatcow/thumbnails/zip60x45.png' => 'af11bf3e', 'rsrc/image/icon/lightbox/close-2.png' => 'cc40e7c8', 'rsrc/image/icon/lightbox/close-hover-2.png' => 'fb5d6d9e', 'rsrc/image/icon/lightbox/left-arrow-2.png' => '8426133b', 'rsrc/image/icon/lightbox/left-arrow-hover-2.png' => '701e5ee3', 'rsrc/image/icon/lightbox/right-arrow-2.png' => '6d5519a0', 'rsrc/image/icon/lightbox/right-arrow-hover-2.png' => '3a04aa21', 'rsrc/image/icon/subscribe.png' => 'd03ed5a5', 'rsrc/image/icon/tango/attachment.png' => 'ecc8022e', 'rsrc/image/icon/tango/edit.png' => '929a1363', 'rsrc/image/icon/tango/go-down.png' => '96d95e43', 'rsrc/image/icon/tango/log.png' => 'b08cc63a', 'rsrc/image/icon/tango/upload.png' => '7bbb7984', 'rsrc/image/icon/unsubscribe.png' => '25725013', 'rsrc/image/lightblue-header.png' => '5c168b6d', 'rsrc/image/loading.gif' => '75d384cc', 'rsrc/image/loading/boating_24.gif' => '5c90f086', 'rsrc/image/loading/compass_24.gif' => 'b36b4f46', 'rsrc/image/loading/loading_24.gif' => '26bc9adc', 'rsrc/image/loading/loading_48.gif' => '6a4994c7', 'rsrc/image/loading/loading_d48.gif' => 'cdcbe900', 'rsrc/image/loading/loading_w24.gif' => '7662fa2b', 'rsrc/image/main_texture.png' => '29a2c5ad', 'rsrc/image/menu_texture.png' => '5a17580d', 'rsrc/image/people/harding.png' => '45aa614e', 'rsrc/image/people/jefferson.png' => 'afca0e53', 'rsrc/image/people/lincoln.png' => '9369126d', 'rsrc/image/people/mckinley.png' => 'fb8f16ce', 'rsrc/image/people/taft.png' => 'd7bc402c', 'rsrc/image/people/washington.png' => '40dd301c', - 'rsrc/image/phortune/balanced.png' => 'f6ba2691', - 'rsrc/image/phortune/paypal.png' => '9747cb33', - 'rsrc/image/phortune/stripe.png' => 'bfedc2ce', - 'rsrc/image/phortune/test.png' => '0235d8a7', - 'rsrc/image/phortune/wepay.png' => 'b37163ce', 'rsrc/image/phrequent_active.png' => 'a466a8ed', 'rsrc/image/phrequent_inactive.png' => 'bfc15a69', 'rsrc/image/search-white.png' => '64cc0d45', 'rsrc/image/search.png' => '82625a7e', 'rsrc/image/sprite-apps-X2.png' => '58294cf3', 'rsrc/image/sprite-apps-X4.png' => '936fc9af', 'rsrc/image/sprite-apps-large-X2.png' => '79e15268', 'rsrc/image/sprite-apps-large.png' => '4d41b94a', 'rsrc/image/sprite-apps-xlarge.png' => 'a751a580', 'rsrc/image/sprite-apps.png' => '5570df20', 'rsrc/image/sprite-conpherence-X2.png' => 'cd2d08d7', 'rsrc/image/sprite-conpherence.png' => 'a5ab2eb7', 'rsrc/image/sprite-docs-X2.png' => '6dc1adad', 'rsrc/image/sprite-docs.png' => '4636297f', 'rsrc/image/sprite-gradient.png' => 'ec15a417', 'rsrc/image/sprite-login-X2.png' => '46f95dcc', 'rsrc/image/sprite-login.png' => '4e0e66ee', 'rsrc/image/sprite-main-header.png' => '83521873', 'rsrc/image/sprite-menu-X2.png' => '39d78f97', 'rsrc/image/sprite-menu.png' => '259dab45', 'rsrc/image/sprite-payments.png' => 'd8576309', 'rsrc/image/sprite-projects-X2.png' => '218fdc8b', 'rsrc/image/sprite-projects.png' => '631ff9a7', 'rsrc/image/sprite-tokens-X2.png' => 'b4776580', 'rsrc/image/sprite-tokens.png' => '25b75533', 'rsrc/image/texture/card-gradient.png' => '815f26e8', 'rsrc/image/texture/dark-menu-hover.png' => '5fa7ece8', 'rsrc/image/texture/dark-menu.png' => '7e22296e', 'rsrc/image/texture/grip.png' => '719404f3', 'rsrc/image/texture/panel-header-gradient.png' => 'e3b8dcfe', 'rsrc/image/texture/phlnx-bg.png' => '8d819209', 'rsrc/image/texture/pholio-background.gif' => 'ba29239c', 'rsrc/image/texture/table_header.png' => '5c433037', 'rsrc/image/texture/table_header_hover.png' => '038ec3b9', 'rsrc/image/texture/table_header_tall.png' => 'd56b434f', 'rsrc/js/application/aphlict/Aphlict.js' => '4a07e8e3', 'rsrc/js/application/aphlict/behavior-aphlict-dropdown.js' => '7d4cc76c', 'rsrc/js/application/aphlict/behavior-aphlict-listen.js' => 'a826c925', 'rsrc/js/application/aphlict/behavior-aphlict-status.js' => '58f7803f', 'rsrc/js/application/auth/behavior-persona-login.js' => '9414ff18', 'rsrc/js/application/config/behavior-reorder-fields.js' => '14a827de', 'rsrc/js/application/conpherence/behavior-menu.js' => 'f0a41b9f', 'rsrc/js/application/conpherence/behavior-pontificate.js' => '85ab3c8e', 'rsrc/js/application/conpherence/behavior-widget-pane.js' => '40b1ff90', 'rsrc/js/application/countdown/timer.js' => '361e3ed3', 'rsrc/js/application/dashboard/behavior-dashboard-async-panel.js' => '469c0d9e', 'rsrc/js/application/dashboard/behavior-dashboard-move-panels.js' => '82439934', 'rsrc/js/application/dashboard/behavior-dashboard-query-panel-select.js' => '453c5375', 'rsrc/js/application/dashboard/behavior-dashboard-tab-panel.js' => 'd4eecc63', 'rsrc/js/application/differential/ChangesetViewManager.js' => 'd2907473', 'rsrc/js/application/differential/DifferentialInlineCommentEditor.js' => 'f2441746', 'rsrc/js/application/differential/behavior-add-reviewers-and-ccs.js' => 'e10f8e18', 'rsrc/js/application/differential/behavior-comment-jump.js' => '4fdb476d', 'rsrc/js/application/differential/behavior-comment-preview.js' => '6932def3', 'rsrc/js/application/differential/behavior-diff-radios.js' => 'e1ff79b1', 'rsrc/js/application/differential/behavior-dropdown-menus.js' => '710f209e', 'rsrc/js/application/differential/behavior-edit-inline-comments.js' => '00861799', 'rsrc/js/application/differential/behavior-keyboard-nav.js' => '8d199d97', 'rsrc/js/application/differential/behavior-populate.js' => 'bdb3e4d0', 'rsrc/js/application/differential/behavior-show-all-comments.js' => '7c273581', 'rsrc/js/application/differential/behavior-show-field-details.js' => 'bba9eedf', 'rsrc/js/application/differential/behavior-show-more.js' => 'dd7e8ef5', 'rsrc/js/application/differential/behavior-toggle-files.js' => 'ca3f91eb', 'rsrc/js/application/differential/behavior-user-select.js' => 'a8d8459d', 'rsrc/js/application/diffusion/DiffusionLocateFileSource.js' => 'b42eddc7', 'rsrc/js/application/diffusion/behavior-audit-preview.js' => 'd835b03a', 'rsrc/js/application/diffusion/behavior-commit-branches.js' => 'bdaf4d04', 'rsrc/js/application/diffusion/behavior-commit-graph.js' => 'f7f1289f', 'rsrc/js/application/diffusion/behavior-jump-to.js' => '9db3d160', 'rsrc/js/application/diffusion/behavior-load-blame.js' => '42126667', 'rsrc/js/application/diffusion/behavior-locate-file.js' => '6d3e1947', 'rsrc/js/application/diffusion/behavior-pull-lastmodified.js' => '2b228192', 'rsrc/js/application/doorkeeper/behavior-doorkeeper-tag.js' => 'e5822781', 'rsrc/js/application/files/behavior-icon-composer.js' => '8ef9ab58', 'rsrc/js/application/files/behavior-launch-icon-composer.js' => '48086888', 'rsrc/js/application/herald/HeraldRuleEditor.js' => '3fc2c8f2', 'rsrc/js/application/herald/PathTypeahead.js' => 'f7fc67ec', 'rsrc/js/application/herald/herald-rule-editor.js' => '7ebaeed3', 'rsrc/js/application/maniphest/behavior-batch-editor.js' => 'f588412e', 'rsrc/js/application/maniphest/behavior-batch-selector.js' => '7b98d7c5', 'rsrc/js/application/maniphest/behavior-line-chart.js' => '22e16ae7', 'rsrc/js/application/maniphest/behavior-list-edit.js' => 'a9f88de2', 'rsrc/js/application/maniphest/behavior-subpriorityeditor.js' => '84845b5b', 'rsrc/js/application/maniphest/behavior-transaction-controls.js' => '44168bad', 'rsrc/js/application/maniphest/behavior-transaction-expand.js' => '5fefb143', 'rsrc/js/application/maniphest/behavior-transaction-preview.js' => 'f8248bc5', 'rsrc/js/application/owners/OwnersPathEditor.js' => 'aa1733d0', 'rsrc/js/application/owners/owners-path-editor.js' => '7a68dda3', 'rsrc/js/application/passphrase/phame-credential-control.js' => '3d51a746', 'rsrc/js/application/phame/phame-post-preview.js' => 'be807912', 'rsrc/js/application/pholio/behavior-pholio-mock-edit.js' => '9c2623f4', 'rsrc/js/application/pholio/behavior-pholio-mock-view.js' => '152178f0', 'rsrc/js/application/phortune/behavior-balanced-payment-form.js' => '3b3e1664', 'rsrc/js/application/phortune/behavior-stripe-payment-form.js' => '1693a296', 'rsrc/js/application/phortune/behavior-test-payment-form.js' => 'ab8d2723', 'rsrc/js/application/phortune/phortune-credit-card-form.js' => '2290aeef', 'rsrc/js/application/policy/behavior-policy-control.js' => 'f3fef818', 'rsrc/js/application/policy/behavior-policy-rule-editor.js' => 'fe9a552f', 'rsrc/js/application/ponder/behavior-votebox.js' => '4e9b766b', 'rsrc/js/application/projects/behavior-boards-dropdown.js' => '0ec56e1d', 'rsrc/js/application/projects/behavior-project-boards.js' => '0676345e', 'rsrc/js/application/projects/behavior-project-create.js' => '065227cc', 'rsrc/js/application/projects/behavior-reorder-columns.js' => 'e1d25dfb', 'rsrc/js/application/releeph/releeph-preview-branch.js' => 'b2b4fbaf', 'rsrc/js/application/releeph/releeph-request-state-change.js' => 'ab836011', 'rsrc/js/application/releeph/releeph-request-typeahead.js' => 'de2e896f', 'rsrc/js/application/repository/repository-crossreference.js' => 'f9539603', 'rsrc/js/application/search/behavior-reorder-queries.js' => 'e9581f08', 'rsrc/js/application/slowvote/behavior-slowvote-embed.js' => 'd6f54db0', 'rsrc/js/application/transactions/behavior-transaction-comment-form.js' => '9f7309fb', 'rsrc/js/application/transactions/behavior-transaction-list.js' => '13c739ea', 'rsrc/js/application/uiexample/JavelinViewExample.js' => 'd4a14807', 'rsrc/js/application/uiexample/ReactorButtonExample.js' => 'd19198c8', 'rsrc/js/application/uiexample/ReactorCheckboxExample.js' => '519705ea', 'rsrc/js/application/uiexample/ReactorFocusExample.js' => '40a6a403', 'rsrc/js/application/uiexample/ReactorInputExample.js' => '886fd850', 'rsrc/js/application/uiexample/ReactorMouseoverExample.js' => '47c794d8', 'rsrc/js/application/uiexample/ReactorRadioExample.js' => '988040b4', 'rsrc/js/application/uiexample/ReactorSelectExample.js' => 'a155550f', 'rsrc/js/application/uiexample/ReactorSendClassExample.js' => '1def2711', 'rsrc/js/application/uiexample/ReactorSendPropertiesExample.js' => 'b1f0ccee', 'rsrc/js/application/uiexample/busy-example.js' => '60479091', 'rsrc/js/application/uiexample/gesture-example.js' => '558829c2', 'rsrc/js/application/uiexample/notification-example.js' => '7a9677fc', 'rsrc/js/core/Busy.js' => '6453c869', 'rsrc/js/core/DragAndDropFileUpload.js' => '8c49f386', 'rsrc/js/core/DraggableList.js' => 'a16ec1c6', 'rsrc/js/core/FileUpload.js' => 'a4ae61bf', 'rsrc/js/core/Hovercard.js' => '7e8468ae', 'rsrc/js/core/KeyboardShortcut.js' => '1ae869f2', 'rsrc/js/core/KeyboardShortcutManager.js' => 'ad7a69ca', 'rsrc/js/core/MultirowRowManager.js' => '41e47dea', 'rsrc/js/core/Notification.js' => '0c6946e7', 'rsrc/js/core/Prefab.js' => 'bbae734c', 'rsrc/js/core/ShapedRequest.js' => '7cbe244b', 'rsrc/js/core/TextAreaUtils.js' => '5c93c52c', 'rsrc/js/core/Title.js' => '5c1c758c', 'rsrc/js/core/ToolTip.js' => '3915d490', 'rsrc/js/core/behavior-active-nav.js' => 'e379b58e', 'rsrc/js/core/behavior-audio-source.js' => '59b251eb', 'rsrc/js/core/behavior-autofocus.js' => '7319e029', 'rsrc/js/core/behavior-choose-control.js' => '6153c708', 'rsrc/js/core/behavior-crop.js' => 'fa0f4fc2', 'rsrc/js/core/behavior-dark-console.js' => '357b6e9b', 'rsrc/js/core/behavior-device.js' => '03d6ed07', 'rsrc/js/core/behavior-drag-and-drop-textarea.js' => '92eb531d', 'rsrc/js/core/behavior-error-log.js' => 'a5d7cf86', 'rsrc/js/core/behavior-fancy-datepicker.js' => 'c51ae228', 'rsrc/js/core/behavior-file-tree.js' => '88236f00', 'rsrc/js/core/behavior-form.js' => '5c54cbf3', 'rsrc/js/core/behavior-gesture.js' => '3ab51e2c', 'rsrc/js/core/behavior-global-drag-and-drop.js' => '07f199d8', 'rsrc/js/core/behavior-high-security-warning.js' => '8fc1c918', 'rsrc/js/core/behavior-history-install.js' => '7ee2b591', 'rsrc/js/core/behavior-hovercard.js' => 'f36e01af', 'rsrc/js/core/behavior-keyboard-pager.js' => 'a8da01f0', 'rsrc/js/core/behavior-keyboard-shortcuts.js' => 'd75709e6', 'rsrc/js/core/behavior-konami.js' => '5bc2cb21', 'rsrc/js/core/behavior-lightbox-attachments.js' => '0720f2cf', 'rsrc/js/core/behavior-line-linker.js' => 'f726d506', 'rsrc/js/core/behavior-more.js' => 'a80d0378', 'rsrc/js/core/behavior-object-selector.js' => '39841ead', 'rsrc/js/core/behavior-oncopy.js' => '2926fff2', 'rsrc/js/core/behavior-phabricator-nav.js' => '14d7a8b8', 'rsrc/js/core/behavior-phabricator-remarkup-assist.js' => 'e32d14ab', 'rsrc/js/core/behavior-refresh-csrf.js' => '7814b593', 'rsrc/js/core/behavior-remarkup-preview.js' => 'f7379f45', 'rsrc/js/core/behavior-reorder-applications.js' => '76b9fc3e', 'rsrc/js/core/behavior-reveal-content.js' => '60821bc7', 'rsrc/js/core/behavior-search-typeahead.js' => 'd712ac5f', 'rsrc/js/core/behavior-select-on-click.js' => '4e3e79a6', 'rsrc/js/core/behavior-toggle-class.js' => 'e566f52c', 'rsrc/js/core/behavior-tokenizer.js' => 'b3a4b884', 'rsrc/js/core/behavior-tooltip.js' => '3ee3408b', 'rsrc/js/core/behavior-watch-anchor.js' => '06e05112', 'rsrc/js/core/behavior-workflow.js' => '0a3f3021', 'rsrc/js/core/phtize.js' => 'd254d646', 'rsrc/js/phui/behavior-phui-object-box-tabs.js' => '2bfa2836', 'rsrc/js/phui/behavior-phui-timeline-dropdown-menu.js' => '4d94d9c3', 'rsrc/js/phuix/PHUIXActionListView.js' => 'b5c256b8', 'rsrc/js/phuix/PHUIXActionView.js' => '6e8cefa4', 'rsrc/js/phuix/PHUIXDropdownMenu.js' => 'bd4c8dca', 'rsrc/swf/aphlict.swf' => 'f19daffb', ), 'symbols' => array( 'aphront-bars' => '231ac33c', 'aphront-contextbar-view-css' => '1c3b0529', 'aphront-dark-console-css' => '6378ef3d', 'aphront-dialog-view-css' => '4dbbe3bb', 'aphront-error-view-css' => '3462dbee', 'aphront-list-filter-view-css' => '2ae43867', 'aphront-multi-column-view-css' => '1b95ab2e', 'aphront-pager-view-css' => '2e3539af', 'aphront-panel-view-css' => '5846dfa2', 'aphront-table-view-css' => 'b22b7216', 'aphront-tokenizer-control-css' => '82ce2142', 'aphront-tooltip-css' => '9c90229d', 'aphront-two-column-view-css' => '16ab3ad2', 'aphront-typeahead-control-css' => 'a989b5b3', 'auth-css' => '1e655982', 'changeset-view-manager' => 'd2907473', 'config-options-css' => '7fedf08b', 'config-welcome-css' => 'b0d16200', 'conpherence-menu-css' => 'e1e0fdf1', 'conpherence-message-pane-css' => '11a393ca', 'conpherence-notification-css' => '04a6e10a', 'conpherence-update-css' => '1099a660', 'conpherence-widget-pane-css' => 'bf275a6c', 'differential-changeset-view-css' => 'b2b71e76', 'differential-core-view-css' => '7ac3cabc', 'differential-inline-comment-editor' => 'f2441746', 'differential-results-table-css' => '181aa9d9', 'differential-revision-add-comment-css' => 'c478bcaa', 'differential-revision-comment-css' => '48186045', 'differential-revision-history-css' => '0e8eb855', 'differential-revision-list-css' => 'f3c47d33', 'differential-table-of-contents-css' => '6bf8e1d2', 'diffusion-icons-css' => '9c5828da', 'diffusion-source-css' => '66fdf661', 'diviner-shared-css' => '38813222', 'font-fontawesome' => '73d075c3', 'font-source-sans-pro' => '91d53463', 'global-drag-and-drop-css' => '697324ad', 'harbormaster-css' => '49d64eb4', 'herald-css' => 'c544dd1c', 'herald-rule-editor' => '3fc2c8f2', 'herald-test-css' => '778b008e', 'inline-comment-summary-css' => '8cfd34e8', 'javelin-aphlict' => '4a07e8e3', 'javelin-behavior' => '61cbc29a', 'javelin-behavior-aphlict-dropdown' => '7d4cc76c', 'javelin-behavior-aphlict-listen' => 'a826c925', 'javelin-behavior-aphlict-status' => '58f7803f', 'javelin-behavior-aphront-basic-tokenizer' => 'b3a4b884', 'javelin-behavior-aphront-crop' => 'fa0f4fc2', 'javelin-behavior-aphront-drag-and-drop-textarea' => '92eb531d', 'javelin-behavior-aphront-form-disable-on-submit' => '5c54cbf3', 'javelin-behavior-aphront-more' => 'a80d0378', 'javelin-behavior-audio-source' => '59b251eb', 'javelin-behavior-audit-preview' => 'd835b03a', 'javelin-behavior-balanced-payment-form' => '3b3e1664', 'javelin-behavior-boards-dropdown' => '0ec56e1d', 'javelin-behavior-choose-control' => '6153c708', 'javelin-behavior-config-reorder-fields' => '14a827de', 'javelin-behavior-conpherence-menu' => 'f0a41b9f', 'javelin-behavior-conpherence-pontificate' => '85ab3c8e', 'javelin-behavior-conpherence-widget-pane' => '40b1ff90', 'javelin-behavior-countdown-timer' => '361e3ed3', 'javelin-behavior-dark-console' => '357b6e9b', 'javelin-behavior-dashboard-async-panel' => '469c0d9e', 'javelin-behavior-dashboard-move-panels' => '82439934', 'javelin-behavior-dashboard-query-panel-select' => '453c5375', 'javelin-behavior-dashboard-tab-panel' => 'd4eecc63', 'javelin-behavior-device' => '03d6ed07', 'javelin-behavior-differential-add-reviewers-and-ccs' => 'e10f8e18', 'javelin-behavior-differential-comment-jump' => '4fdb476d', 'javelin-behavior-differential-diff-radios' => 'e1ff79b1', 'javelin-behavior-differential-dropdown-menus' => '710f209e', 'javelin-behavior-differential-edit-inline-comments' => '00861799', 'javelin-behavior-differential-feedback-preview' => '6932def3', 'javelin-behavior-differential-keyboard-navigation' => '8d199d97', 'javelin-behavior-differential-populate' => 'bdb3e4d0', 'javelin-behavior-differential-show-field-details' => 'bba9eedf', 'javelin-behavior-differential-show-more' => 'dd7e8ef5', 'javelin-behavior-differential-toggle-files' => 'ca3f91eb', 'javelin-behavior-differential-user-select' => 'a8d8459d', 'javelin-behavior-diffusion-commit-branches' => 'bdaf4d04', 'javelin-behavior-diffusion-commit-graph' => 'f7f1289f', 'javelin-behavior-diffusion-jump-to' => '9db3d160', 'javelin-behavior-diffusion-locate-file' => '6d3e1947', 'javelin-behavior-diffusion-pull-lastmodified' => '2b228192', 'javelin-behavior-doorkeeper-tag' => 'e5822781', 'javelin-behavior-error-log' => 'a5d7cf86', 'javelin-behavior-fancy-datepicker' => 'c51ae228', 'javelin-behavior-global-drag-and-drop' => '07f199d8', 'javelin-behavior-herald-rule-editor' => '7ebaeed3', 'javelin-behavior-high-security-warning' => '8fc1c918', 'javelin-behavior-history-install' => '7ee2b591', 'javelin-behavior-icon-composer' => '8ef9ab58', 'javelin-behavior-konami' => '5bc2cb21', 'javelin-behavior-launch-icon-composer' => '48086888', 'javelin-behavior-lightbox-attachments' => '0720f2cf', 'javelin-behavior-line-chart' => '22e16ae7', 'javelin-behavior-load-blame' => '42126667', 'javelin-behavior-maniphest-batch-editor' => 'f588412e', 'javelin-behavior-maniphest-batch-selector' => '7b98d7c5', 'javelin-behavior-maniphest-list-editor' => 'a9f88de2', 'javelin-behavior-maniphest-subpriority-editor' => '84845b5b', 'javelin-behavior-maniphest-transaction-controls' => '44168bad', 'javelin-behavior-maniphest-transaction-expand' => '5fefb143', 'javelin-behavior-maniphest-transaction-preview' => 'f8248bc5', 'javelin-behavior-owners-path-editor' => '7a68dda3', 'javelin-behavior-passphrase-credential-control' => '3d51a746', 'javelin-behavior-persona-login' => '9414ff18', 'javelin-behavior-phabricator-active-nav' => 'e379b58e', 'javelin-behavior-phabricator-autofocus' => '7319e029', 'javelin-behavior-phabricator-busy-example' => '60479091', 'javelin-behavior-phabricator-file-tree' => '88236f00', 'javelin-behavior-phabricator-gesture' => '3ab51e2c', 'javelin-behavior-phabricator-gesture-example' => '558829c2', 'javelin-behavior-phabricator-hovercards' => 'f36e01af', 'javelin-behavior-phabricator-keyboard-pager' => 'a8da01f0', 'javelin-behavior-phabricator-keyboard-shortcuts' => 'd75709e6', 'javelin-behavior-phabricator-line-linker' => 'f726d506', 'javelin-behavior-phabricator-nav' => '14d7a8b8', 'javelin-behavior-phabricator-notification-example' => '7a9677fc', 'javelin-behavior-phabricator-object-selector' => '39841ead', 'javelin-behavior-phabricator-oncopy' => '2926fff2', 'javelin-behavior-phabricator-remarkup-assist' => 'e32d14ab', 'javelin-behavior-phabricator-reveal-content' => '60821bc7', 'javelin-behavior-phabricator-search-typeahead' => 'd712ac5f', 'javelin-behavior-phabricator-show-all-transactions' => '7c273581', 'javelin-behavior-phabricator-tooltips' => '3ee3408b', 'javelin-behavior-phabricator-transaction-comment-form' => '9f7309fb', 'javelin-behavior-phabricator-transaction-list' => '13c739ea', 'javelin-behavior-phabricator-watch-anchor' => '06e05112', 'javelin-behavior-phame-post-preview' => 'be807912', 'javelin-behavior-pholio-mock-edit' => '9c2623f4', 'javelin-behavior-pholio-mock-view' => '152178f0', 'javelin-behavior-phui-object-box-tabs' => '2bfa2836', 'javelin-behavior-phui-timeline-dropdown-menu' => '4d94d9c3', 'javelin-behavior-policy-control' => 'f3fef818', 'javelin-behavior-policy-rule-editor' => 'fe9a552f', 'javelin-behavior-ponder-votebox' => '4e9b766b', 'javelin-behavior-project-boards' => '0676345e', 'javelin-behavior-project-create' => '065227cc', 'javelin-behavior-refresh-csrf' => '7814b593', 'javelin-behavior-releeph-preview-branch' => 'b2b4fbaf', 'javelin-behavior-releeph-request-state-change' => 'ab836011', 'javelin-behavior-releeph-request-typeahead' => 'de2e896f', 'javelin-behavior-remarkup-preview' => 'f7379f45', 'javelin-behavior-reorder-applications' => '76b9fc3e', 'javelin-behavior-reorder-columns' => 'e1d25dfb', 'javelin-behavior-repository-crossreference' => 'f9539603', 'javelin-behavior-search-reorder-queries' => 'e9581f08', 'javelin-behavior-select-on-click' => '4e3e79a6', 'javelin-behavior-slowvote-embed' => 'd6f54db0', 'javelin-behavior-stripe-payment-form' => '1693a296', 'javelin-behavior-test-payment-form' => 'ab8d2723', 'javelin-behavior-toggle-class' => 'e566f52c', 'javelin-behavior-view-placeholder' => '2fa810fc', 'javelin-behavior-workflow' => '0a3f3021', 'javelin-color' => '7e41274a', 'javelin-cookie' => '6b3dcf44', 'javelin-diffusion-locate-file-source' => 'b42eddc7', 'javelin-dom' => 'c4569c05', 'javelin-dynval' => 'f6555212', 'javelin-event' => '85ea0626', 'javelin-fx' => '54b612ba', 'javelin-history' => 'c60f4327', 'javelin-install' => '1ffb3a9c', 'javelin-json' => '69adf288', 'javelin-magical-init' => 'b88ab49e', 'javelin-mask' => '8a41885b', 'javelin-reactor' => '77b1cf6f', 'javelin-reactor-dom' => 'b6d401d6', 'javelin-reactor-node-calmer' => '76f4ebed', 'javelin-reactornode' => 'b4c30592', 'javelin-request' => '97258e55', 'javelin-resource' => '0f81f8df', 'javelin-routable' => 'b3e7d692', 'javelin-router' => '29274e2b', 'javelin-stratcom' => '8b0ad945', 'javelin-tokenizer' => '9f06389f', 'javelin-typeahead' => 'e614d22b', 'javelin-typeahead-composite-source' => '503e17fd', 'javelin-typeahead-normalizer' => '1c22377d', 'javelin-typeahead-ondemand-source' => '8b3fd187', 'javelin-typeahead-preloaded-source' => '54f314a0', 'javelin-typeahead-source' => 'fcba4ecc', 'javelin-typeahead-static-source' => '316b8fa1', 'javelin-uri' => '6eff08aa', 'javelin-util' => 'e7995242', 'javelin-vector' => 'cc1bd0b0', 'javelin-view' => '0f764c35', 'javelin-view-html' => 'e5b406f9', 'javelin-view-interpreter' => '0c33c1a0', 'javelin-view-renderer' => '6c2b09a2', 'javelin-view-visitor' => 'efe49472', 'javelin-workflow' => 'd149e002', 'lightbox-attachment-css' => '7acac05d', 'maniphest-batch-editor' => '8f380ebc', 'maniphest-report-css' => '6fc16517', 'maniphest-task-edit-css' => '8e23031b', 'maniphest-task-summary-css' => '00c3be7a', 'multirow-row-manager' => '41e47dea', 'owners-path-editor' => 'aa1733d0', 'owners-path-editor-css' => '2f00933b', 'paste-css' => 'aa1767d1', 'path-typeahead' => 'f7fc67ec', 'people-profile-css' => '5402f7a5', 'phabricator-action-list-view-css' => '9ee9910a', 'phabricator-application-launch-view-css' => '5d71008f', 'phabricator-busy' => '6453c869', 'phabricator-chatlog-css' => '852140ff', 'phabricator-content-source-view-css' => '4b8b05d4', 'phabricator-core-css' => '40151074', 'phabricator-countdown-css' => '86b7b0a0', 'phabricator-crumbs-view-css' => 'a49339de', 'phabricator-dashboard-css' => 'a2bfdcbf', 'phabricator-drag-and-drop-file-upload' => '8c49f386', 'phabricator-draggable-list' => 'a16ec1c6', 'phabricator-fatal-config-template-css' => '25d446d6', 'phabricator-feed-css' => '7bfc6f12', 'phabricator-file-upload' => 'a4ae61bf', 'phabricator-filetree-view-css' => 'fccf9f82', 'phabricator-flag-css' => '5337623f', 'phabricator-hovercard' => '7e8468ae', 'phabricator-hovercard-view-css' => '893f4783', 'phabricator-keyboard-shortcut' => '1ae869f2', 'phabricator-keyboard-shortcut-manager' => 'ad7a69ca', 'phabricator-main-menu-view' => 'aceca0e9', 'phabricator-nav-view-css' => '9283c2df', 'phabricator-notification' => '0c6946e7', 'phabricator-notification-css' => 'ef2c9b34', 'phabricator-notification-menu-css' => '6aa0a74b', 'phabricator-object-selector-css' => '029a133d', 'phabricator-phtize' => 'd254d646', 'phabricator-prefab' => 'bbae734c', 'phabricator-profile-css' => '28f433ef', 'phabricator-remarkup-css' => '45313445', 'phabricator-search-results-css' => 'f240504c', 'phabricator-shaped-request' => '7cbe244b', 'phabricator-side-menu-view-css' => 'a2ccd7bd', 'phabricator-slowvote-css' => '266df6a1', 'phabricator-source-code-view-css' => '7d346aa4', 'phabricator-standard-page-view' => '3f5b9311', 'phabricator-textareautils' => '5c93c52c', 'phabricator-title' => '5c1c758c', 'phabricator-tooltip' => '3915d490', 'phabricator-transaction-view-css' => '5d0cae25', 'phabricator-ui-example-css' => '528b19de', 'phabricator-uiexample-javelin-view' => 'd4a14807', 'phabricator-uiexample-reactor-button' => 'd19198c8', 'phabricator-uiexample-reactor-checkbox' => '519705ea', 'phabricator-uiexample-reactor-focus' => '40a6a403', 'phabricator-uiexample-reactor-input' => '886fd850', 'phabricator-uiexample-reactor-mouseover' => '47c794d8', 'phabricator-uiexample-reactor-radio' => '988040b4', 'phabricator-uiexample-reactor-select' => 'a155550f', 'phabricator-uiexample-reactor-sendclass' => '1def2711', 'phabricator-uiexample-reactor-sendproperties' => 'b1f0ccee', 'phabricator-zindex-css' => '44e1d311', 'phame-css' => '19ecc703', 'pholio-css' => '47dffb9c', 'pholio-edit-css' => '3ad9d1ee', 'pholio-inline-comments-css' => '8e545e49', 'phortune-credit-card-form' => '2290aeef', 'phortune-credit-card-form-css' => 'b25b4beb', 'phortune-css' => '9149f103', 'phrequent-css' => 'ffc185ad', 'phriction-document-css' => '7d7f0071', 'phui-action-header-view-css' => '83e2cc86', 'phui-box-css' => '7b3a2eed', 'phui-button-css' => 'c7412aa1', 'phui-calendar-css' => '5e1ad989', 'phui-calendar-day-css' => 'de035c8a', 'phui-calendar-list-css' => 'c1d0ca59', 'phui-calendar-month-css' => 'a92e47d2', 'phui-document-view-css' => 'a5615198', 'phui-feed-story-css' => '55dc7732', 'phui-font-icon-base-css' => 'eb84f033', 'phui-fontkit-css' => 'fff25cfa', 'phui-form-css' => 'b78ec020', 'phui-form-view-css' => 'a2d72756', 'phui-header-view-css' => '39594ac0', 'phui-icon-view-css' => 'b4963a4f', 'phui-image-mask-css' => '5a8b09c8', 'phui-info-panel-css' => '27ea50a1', 'phui-list-view-css' => '43ed2d93', 'phui-object-box-css' => 'e9f7e938', 'phui-object-item-list-view-css' => 'e1e6425f', 'phui-pinboard-view-css' => '3dd4a269', 'phui-property-list-view-css' => '86f9df88', 'phui-remarkup-preview-css' => '19ad512b', 'phui-spacing-css' => '042804d6', 'phui-status-list-view-css' => '2f562399', 'phui-tag-view-css' => 'c4158073', 'phui-text-css' => '23e9b4b7', 'phui-timeline-view-css' => 'bbd990d0', 'phui-workboard-view-css' => '2bf82d00', 'phui-workpanel-view-css' => '198c7e6c', 'phuix-action-list-view' => 'b5c256b8', 'phuix-action-view' => '6e8cefa4', 'phuix-dropdown-menu' => 'bd4c8dca', 'policy-css' => '957ea14c', 'policy-edit-css' => '05cca26a', 'policy-transaction-detail-css' => '82100a43', 'ponder-comment-table-css' => '6cdccea7', 'ponder-feed-view-css' => 'e62615b6', 'ponder-post-css' => 'ebab8a70', 'ponder-vote-css' => '8ed6ed8b', 'project-icon-css' => 'c2ecb7f1', 'raphael-core' => '51ee6b43', 'raphael-g' => '40dde778', 'raphael-g-line' => '40da039e', 'releeph-core' => '9b3c5733', 'releeph-preview-branch' => 'b7a6f4a5', 'releeph-request-differential-create-dialog' => '8d8b92cd', 'releeph-request-typeahead-css' => '667a48ae', 'setup-issue-css' => '8f852bc0', 'sprite-apps-css' => 'd5baed0f', 'sprite-apps-large-css' => '20ec0cc0', 'sprite-conpherence-css' => '3b4a0487', 'sprite-docs-css' => '5f65d0da', 'sprite-gradient-css' => '4bdb98a7', 'sprite-login-css' => 'cf08ac44', 'sprite-main-header-css' => '92720ee2', 'sprite-menu-css' => '28281e16', 'sprite-payments-css' => 'cc085d44', 'sprite-projects-css' => '7578fa56', 'sprite-tokens-css' => '1706b943', 'syntax-highlighting-css' => '863f3cd8', 'tokens-css' => '3d0f239e', ), 'requires' => array( '00861799' => array( 'javelin-behavior', 'javelin-stratcom', 'javelin-dom', 'javelin-util', 'javelin-vector', 'differential-inline-comment-editor', ), '029a133d' => array( 'aphront-dialog-view-css', ), '03d6ed07' => array( 'javelin-behavior', 'javelin-stratcom', 'javelin-dom', 'javelin-vector', 'javelin-install', ), '065227cc' => array( 'javelin-behavior', 'javelin-dom', 'javelin-stratcom', 'javelin-workflow', ), '0676345e' => array( 'javelin-behavior', 'javelin-dom', 'javelin-util', 'javelin-vector', 'javelin-stratcom', 'javelin-workflow', 'phabricator-draggable-list', ), '06e05112' => array( 'javelin-behavior', 'javelin-stratcom', 'javelin-dom', 'javelin-vector', ), '0720f2cf' => array( 'javelin-behavior', 'javelin-stratcom', 'javelin-dom', 'javelin-mask', 'javelin-util', 'phabricator-busy', ), '07f199d8' => array( 'javelin-behavior', 'javelin-dom', 'javelin-uri', 'javelin-mask', 'phabricator-drag-and-drop-file-upload', ), '0a3f3021' => array( 'javelin-behavior', 'javelin-stratcom', 'javelin-workflow', 'javelin-dom', 'javelin-router', ), '0c33c1a0' => array( 'javelin-view', 'javelin-install', 'javelin-dom', ), '0c6946e7' => array( 'javelin-install', 'javelin-dom', 'javelin-stratcom', 'javelin-util', 'phabricator-notification-css', ), '0ec56e1d' => array( 'javelin-behavior', 'javelin-dom', 'javelin-stratcom', 'phuix-dropdown-menu', ), '0f764c35' => array( 'javelin-install', 'javelin-util', ), '0f81f8df' => array( 'javelin-util', 'javelin-uri', 'javelin-install', ), '13c739ea' => array( 'javelin-behavior', 'javelin-stratcom', 'javelin-workflow', 'javelin-dom', 'javelin-uri', 'phabricator-textareautils', ), '14a827de' => array( 'javelin-behavior', 'javelin-stratcom', 'javelin-dom', 'javelin-json', 'phabricator-draggable-list', ), '14d7a8b8' => array( 'javelin-behavior', 'javelin-behavior-device', 'javelin-stratcom', 'javelin-dom', 'javelin-magical-init', 'javelin-vector', 'javelin-request', 'javelin-util', ), '152178f0' => array( 'javelin-behavior', 'javelin-util', 'javelin-stratcom', 'javelin-dom', 'javelin-vector', 'javelin-magical-init', 'javelin-request', 'javelin-history', 'javelin-workflow', 'javelin-mask', 'javelin-behavior-device', 'phabricator-keyboard-shortcut', ), '1693a296' => array( 'javelin-behavior', 'javelin-dom', 'phortune-credit-card-form', ), '1ae869f2' => array( 'javelin-install', 'javelin-util', 'phabricator-keyboard-shortcut-manager', ), '1c22377d' => array( 'javelin-install', ), '1def2711' => array( 'javelin-install', 'javelin-dom', 'javelin-reactor-dom', ), '1ffb3a9c' => array( 'javelin-util', 'javelin-magical-init', ), '2290aeef' => array( 'javelin-install', 'javelin-dom', 'javelin-json', 'javelin-workflow', 'javelin-util', ), '22e16ae7' => array( 'javelin-behavior', 'javelin-dom', 'javelin-vector', ), '2926fff2' => array( 'javelin-behavior', 'javelin-dom', ), '29274e2b' => array( 'javelin-install', 'javelin-util', ), '2b228192' => array( 'javelin-behavior', 'javelin-dom', 'javelin-util', 'javelin-workflow', 'javelin-json', ), '2bfa2836' => array( 'javelin-behavior', 'javelin-stratcom', 'javelin-dom', ), '2fa810fc' => array( 'javelin-behavior', 'javelin-dom', 'javelin-view-renderer', 'javelin-install', ), '316b8fa1' => array( 'javelin-install', 'javelin-typeahead-source', ), '357b6e9b' => array( 'javelin-behavior', 'javelin-stratcom', 'javelin-util', 'javelin-dom', 'javelin-request', 'phabricator-keyboard-shortcut', ), '361e3ed3' => array( 'javelin-behavior', 'javelin-dom', ), '3915d490' => array( 'javelin-install', 'javelin-util', 'javelin-dom', 'javelin-vector', ), '39841ead' => array( 'javelin-behavior', 'javelin-dom', 'javelin-request', 'javelin-util', ), '3ab51e2c' => array( 'javelin-behavior', 'javelin-behavior-device', 'javelin-stratcom', 'javelin-vector', 'javelin-dom', 'javelin-magical-init', ), '3b3e1664' => array( 'javelin-behavior', 'javelin-dom', 'phortune-credit-card-form', ), '3d51a746' => array( 'javelin-behavior', 'javelin-dom', 'javelin-stratcom', 'javelin-workflow', 'javelin-util', 'javelin-uri', ), '3ee3408b' => array( 'javelin-behavior', 'javelin-behavior-device', 'javelin-stratcom', 'phabricator-tooltip', ), '3fc2c8f2' => array( 'multirow-row-manager', 'javelin-install', 'javelin-util', 'javelin-dom', 'javelin-stratcom', 'javelin-json', 'phabricator-prefab', ), '40a6a403' => array( 'javelin-install', 'javelin-dom', 'javelin-reactor-dom', ), '40b1ff90' => array( 'javelin-behavior', 'javelin-dom', 'javelin-stratcom', 'javelin-workflow', 'javelin-util', 'phabricator-notification', 'javelin-behavior-device', 'phuix-dropdown-menu', 'phuix-action-list-view', 'phuix-action-view', ), '41e47dea' => array( 'javelin-install', 'javelin-stratcom', 'javelin-dom', 'javelin-util', ), '44168bad' => array( 'javelin-behavior', 'javelin-dom', 'phabricator-prefab', ), '453c5375' => array( 'javelin-behavior', 'javelin-dom', ), '469c0d9e' => array( 'javelin-behavior', 'javelin-dom', 'javelin-workflow', ), '47c794d8' => array( 'javelin-install', 'javelin-dom', 'javelin-reactor-dom', ), '4a07e8e3' => array( 'javelin-install', 'javelin-util', ), '4d94d9c3' => array( 'javelin-behavior', 'javelin-stratcom', 'javelin-dom', 'phuix-dropdown-menu', ), '4e3e79a6' => array( 'javelin-behavior', 'javelin-stratcom', 'javelin-dom', ), '4e9b766b' => array( 'javelin-behavior', 'javelin-dom', 'javelin-util', 'javelin-stratcom', 'javelin-request', ), '4fdb476d' => array( 'javelin-behavior', 'javelin-stratcom', 'javelin-dom', ), '503e17fd' => array( 'javelin-install', 'javelin-typeahead-source', 'javelin-util', ), '519705ea' => array( 'javelin-install', 'javelin-dom', 'javelin-reactor-dom', ), '54b612ba' => array( 'javelin-color', 'javelin-install', 'javelin-util', ), '54f314a0' => array( 'javelin-install', 'javelin-util', 'javelin-request', 'javelin-typeahead-source', ), '558829c2' => array( 'javelin-stratcom', 'javelin-behavior', 'javelin-vector', 'javelin-dom', ), '58f7803f' => array( 'javelin-behavior', 'javelin-aphlict', 'phabricator-phtize', 'javelin-dom', ), '59b251eb' => array( 'javelin-behavior', 'javelin-stratcom', 'javelin-vector', 'javelin-dom', ), '5bc2cb21' => array( 'javelin-behavior', 'javelin-stratcom', ), '5c1c758c' => array( 'javelin-install', ), '5c54cbf3' => array( 'javelin-behavior', 'javelin-stratcom', 'javelin-dom', ), '5c93c52c' => array( 'javelin-install', 'javelin-dom', 'javelin-vector', ), '5fefb143' => array( 'javelin-behavior', 'javelin-dom', 'javelin-workflow', 'javelin-stratcom', ), '60821bc7' => array( 'javelin-behavior', 'javelin-stratcom', 'javelin-dom', ), '6153c708' => array( 'javelin-behavior', 'javelin-stratcom', 'javelin-dom', 'javelin-workflow', ), '61cbc29a' => array( 'javelin-magical-init', 'javelin-util', ), '6453c869' => array( 'javelin-install', 'javelin-dom', 'javelin-fx', ), '6932def3' => array( 'javelin-behavior', 'javelin-stratcom', 'javelin-dom', 'javelin-request', 'javelin-util', 'phabricator-shaped-request', ), '69adf288' => array( 'javelin-install', ), '6b3dcf44' => array( 'javelin-install', 'javelin-util', ), '6c2b09a2' => array( 'javelin-install', 'javelin-util', ), '6d3e1947' => array( 'javelin-behavior', 'javelin-diffusion-locate-file-source', 'javelin-dom', 'javelin-typeahead', 'javelin-uri', ), '6e8cefa4' => array( 'javelin-install', 'javelin-dom', 'javelin-util', ), '6eff08aa' => array( 'javelin-install', 'javelin-util', 'javelin-stratcom', ), '710f209e' => array( 'javelin-behavior', 'javelin-dom', 'javelin-util', 'javelin-stratcom', 'javelin-workflow', 'phuix-dropdown-menu', 'phuix-action-list-view', 'phuix-action-view', 'phabricator-phtize', 'changeset-view-manager', ), '7319e029' => array( 'javelin-behavior', 'javelin-dom', ), '76b9fc3e' => array( 'javelin-behavior', 'javelin-stratcom', 'javelin-workflow', 'javelin-dom', 'phabricator-draggable-list', ), '76f4ebed' => array( 'javelin-install', 'javelin-reactor', 'javelin-util', ), '77b1cf6f' => array( 'javelin-install', 'javelin-util', ), '7814b593' => array( 'javelin-request', 'javelin-behavior', 'javelin-dom', 'javelin-router', 'javelin-util', 'phabricator-busy', ), '7a68dda3' => array( 'owners-path-editor', 'javelin-behavior', ), '7a9677fc' => array( 'phabricator-notification', 'javelin-stratcom', 'javelin-behavior', ), '7b98d7c5' => array( 'javelin-behavior', 'javelin-dom', 'javelin-stratcom', 'javelin-util', ), '7c273581' => array( 'javelin-behavior', 'javelin-stratcom', 'javelin-dom', ), '7cbe244b' => array( 'javelin-install', 'javelin-util', 'javelin-request', 'javelin-router', ), '7d4cc76c' => array( 'javelin-behavior', 'javelin-request', 'javelin-stratcom', 'javelin-vector', 'javelin-dom', 'javelin-uri', 'javelin-behavior-device', 'phabricator-title', ), '7e41274a' => array( 'javelin-install', ), '7e8468ae' => array( 'javelin-install', 'javelin-dom', 'javelin-vector', 'javelin-request', 'javelin-uri', ), '7ebaeed3' => array( 'herald-rule-editor', 'javelin-behavior', ), '7ee2b591' => array( 'javelin-behavior', 'javelin-history', ), '82ce2142' => array( 'aphront-typeahead-control-css', ), '84845b5b' => array( 'javelin-behavior', 'javelin-dom', 'javelin-stratcom', 'javelin-workflow', 'phabricator-draggable-list', ), '85ab3c8e' => array( 'javelin-behavior', 'javelin-dom', 'javelin-util', 'javelin-workflow', 'javelin-stratcom', ), '85ea0626' => array( 'javelin-install', ), '88236f00' => array( 'javelin-behavior', 'phabricator-keyboard-shortcut', 'javelin-stratcom', ), '886fd850' => array( 'javelin-install', 'javelin-reactor-dom', 'javelin-view-html', 'javelin-view-interpreter', 'javelin-view-renderer', ), '8a41885b' => array( 'javelin-install', 'javelin-dom', ), '8b0ad945' => array( 'javelin-install', 'javelin-event', 'javelin-util', 'javelin-magical-init', ), '8b3fd187' => array( 'javelin-install', 'javelin-util', 'javelin-request', 'javelin-typeahead-source', ), '8c49f386' => array( 'javelin-install', 'javelin-util', 'javelin-request', 'javelin-dom', 'javelin-uri', 'phabricator-file-upload', ), '8d199d97' => array( 'javelin-behavior', 'javelin-dom', 'javelin-stratcom', 'phabricator-keyboard-shortcut', ), '8ef9ab58' => array( 'javelin-behavior', 'javelin-dom', 'javelin-stratcom', ), '8fc1c918' => array( 'javelin-behavior', 'javelin-uri', 'phabricator-notification', ), '92eb531d' => array( 'javelin-behavior', 'javelin-dom', 'phabricator-drag-and-drop-file-upload', 'phabricator-textareautils', ), '9414ff18' => array( 'javelin-behavior', 'javelin-resource', 'javelin-stratcom', 'javelin-workflow', 'javelin-util', ), '97258e55' => array( 'javelin-install', 'javelin-stratcom', 'javelin-util', 'javelin-behavior', 'javelin-json', 'javelin-dom', 'javelin-resource', 'javelin-routable', ), '988040b4' => array( 'javelin-install', 'javelin-dom', 'javelin-reactor-dom', ), '9c2623f4' => array( 'javelin-behavior', 'javelin-stratcom', 'javelin-dom', 'javelin-workflow', 'phabricator-phtize', 'phabricator-drag-and-drop-file-upload', 'phabricator-draggable-list', ), '9db3d160' => array( 'javelin-behavior', 'javelin-vector', 'javelin-dom', ), '9f06389f' => array( 'javelin-dom', 'javelin-util', 'javelin-stratcom', 'javelin-install', ), '9f7309fb' => array( 'javelin-behavior', 'javelin-dom', 'javelin-util', 'javelin-request', 'phabricator-shaped-request', ), 'a155550f' => array( 'javelin-install', 'javelin-dom', 'javelin-reactor-dom', ), 'a16ec1c6' => array( 'javelin-install', 'javelin-dom', 'javelin-stratcom', 'javelin-util', 'javelin-vector', 'javelin-magical-init', ), 'a4ae61bf' => array( 'javelin-install', 'javelin-dom', 'phabricator-notification', ), 'a5d7cf86' => array( 'javelin-dom', ), 'a80d0378' => array( 'javelin-behavior', 'javelin-stratcom', 'javelin-dom', ), 'a826c925' => array( 'javelin-behavior', 'javelin-aphlict', 'javelin-stratcom', 'javelin-request', 'javelin-uri', 'javelin-dom', 'javelin-json', 'javelin-router', 'javelin-util', 'phabricator-notification', ), 'a8d8459d' => array( 'javelin-behavior', 'javelin-dom', 'javelin-stratcom', ), 'a8da01f0' => array( 'javelin-behavior', 'javelin-uri', 'phabricator-keyboard-shortcut', ), 'a9f88de2' => array( 'javelin-behavior', 'javelin-dom', 'javelin-stratcom', 'javelin-workflow', 'javelin-fx', 'javelin-util', ), 'aa1733d0' => array( 'multirow-row-manager', 'javelin-install', 'path-typeahead', 'javelin-dom', 'javelin-util', 'phabricator-prefab', ), 'ab836011' => array( 'javelin-behavior', 'javelin-dom', 'javelin-stratcom', 'javelin-workflow', 'javelin-util', 'phabricator-keyboard-shortcut', ), 'ab8d2723' => array( 'javelin-behavior', 'javelin-dom', 'phortune-credit-card-form', ), 'ad7a69ca' => array( 'javelin-install', 'javelin-util', 'javelin-stratcom', 'javelin-dom', 'javelin-vector', ), 'b1f0ccee' => array( 'javelin-install', 'javelin-dom', 'javelin-reactor-dom', ), 'b2b4fbaf' => array( 'javelin-behavior', 'javelin-dom', 'javelin-uri', 'javelin-request', ), 'b3a4b884' => array( 'javelin-behavior', 'phabricator-prefab', ), 'b3e7d692' => array( 'javelin-install', ), 'b42eddc7' => array( 'javelin-install', 'javelin-dom', 'javelin-typeahead-preloaded-source', 'javelin-util', ), 'b4c30592' => array( 'javelin-install', 'javelin-reactor', 'javelin-util', 'javelin-reactor-node-calmer', ), 'b5c256b8' => array( 'javelin-install', 'javelin-dom', ), 'b6d401d6' => array( 'javelin-dom', 'javelin-dynval', 'javelin-reactor', 'javelin-reactornode', 'javelin-install', 'javelin-util', ), 'bba9eedf' => array( 'javelin-behavior', 'javelin-stratcom', 'javelin-dom', ), 'bbae734c' => array( 'javelin-install', 'javelin-util', 'javelin-dom', 'javelin-typeahead', 'javelin-tokenizer', 'javelin-typeahead-preloaded-source', 'javelin-typeahead-ondemand-source', 'javelin-dom', 'javelin-stratcom', 'javelin-util', ), 'bd4c8dca' => array( 'javelin-install', 'javelin-util', 'javelin-dom', 'javelin-vector', 'javelin-stratcom', ), 'bdaf4d04' => array( 'javelin-behavior', 'javelin-dom', 'javelin-util', 'javelin-request', ), 'bdb3e4d0' => array( 'javelin-behavior', 'javelin-dom', 'javelin-stratcom', 'phabricator-tooltip', 'changeset-view-manager', ), 'be807912' => array( 'javelin-behavior', 'javelin-dom', 'javelin-util', 'phabricator-shaped-request', ), 'c4569c05' => array( 'javelin-magical-init', 'javelin-install', 'javelin-util', 'javelin-vector', 'javelin-stratcom', ), 'c51ae228' => array( 'javelin-behavior', 'javelin-util', 'javelin-dom', 'javelin-stratcom', 'javelin-vector', ), 'c60f4327' => array( 'javelin-stratcom', 'javelin-install', 'javelin-uri', 'javelin-util', ), 'ca3f91eb' => array( 'javelin-behavior', 'javelin-dom', 'javelin-stratcom', 'phabricator-phtize', ), 'cc1bd0b0' => array( 'javelin-install', 'javelin-event', ), 'd149e002' => array( 'javelin-stratcom', 'javelin-request', 'javelin-dom', 'javelin-vector', 'javelin-install', 'javelin-util', 'javelin-mask', 'javelin-uri', 'javelin-routable', ), 'd19198c8' => array( 'javelin-install', 'javelin-dom', 'javelin-util', 'javelin-dynval', 'javelin-reactor-dom', ), 'd254d646' => array( 'javelin-util', ), 'd2907473' => array( 'javelin-dom', 'javelin-util', 'javelin-stratcom', 'javelin-install', 'javelin-workflow', 'javelin-router', 'javelin-behavior-device', 'javelin-vector', ), 'd4a14807' => array( 'javelin-install', 'javelin-dom', 'javelin-view', ), 'd4eecc63' => array( 'javelin-behavior', 'javelin-dom', 'javelin-stratcom', ), 'd6f54db0' => array( 'javelin-behavior', 'javelin-request', 'javelin-stratcom', 'javelin-dom', ), 'd712ac5f' => array( 'javelin-behavior', 'javelin-typeahead-ondemand-source', 'javelin-typeahead', 'javelin-dom', 'javelin-uri', 'javelin-util', 'javelin-stratcom', 'phabricator-prefab', ), 'd75709e6' => array( 'javelin-behavior', 'javelin-workflow', 'javelin-json', 'javelin-dom', 'phabricator-keyboard-shortcut', ), 'd835b03a' => array( 'javelin-behavior', 'javelin-dom', 'javelin-util', 'phabricator-shaped-request', ), 'dd7e8ef5' => array( 'javelin-behavior', 'javelin-dom', 'javelin-workflow', 'javelin-util', 'javelin-stratcom', ), 'de2e896f' => array( 'javelin-behavior', 'javelin-dom', 'javelin-typeahead', 'javelin-typeahead-ondemand-source', 'javelin-dom', ), 'e10f8e18' => array( 'javelin-behavior', 'javelin-dom', 'phabricator-prefab', ), 'e1d25dfb' => array( 'javelin-behavior', 'javelin-stratcom', 'javelin-workflow', 'javelin-dom', 'phabricator-draggable-list', ), 'e1ff79b1' => array( 'javelin-behavior', 'javelin-stratcom', 'javelin-dom', ), 'e32d14ab' => array( 'javelin-behavior', 'javelin-stratcom', 'javelin-dom', 'phabricator-phtize', 'phabricator-textareautils', 'javelin-workflow', 'javelin-vector', ), 'e379b58e' => array( 'javelin-behavior', 'javelin-stratcom', 'javelin-vector', 'javelin-dom', 'javelin-uri', ), 'e566f52c' => array( 'javelin-behavior', 'javelin-stratcom', 'javelin-dom', ), 'e5822781' => array( 'javelin-behavior', 'javelin-dom', 'javelin-json', 'javelin-workflow', 'javelin-magical-init', ), 'e5b406f9' => array( 'javelin-install', 'javelin-dom', 'javelin-view-visitor', 'javelin-util', ), 'e614d22b' => array( 'javelin-install', 'javelin-dom', 'javelin-vector', 'javelin-util', ), 'e9581f08' => array( 'javelin-behavior', 'javelin-stratcom', 'javelin-workflow', 'javelin-dom', 'phabricator-draggable-list', ), 'efe49472' => array( 'javelin-install', 'javelin-util', ), 'f0a41b9f' => array( 'javelin-behavior', 'javelin-dom', 'javelin-util', 'javelin-stratcom', 'javelin-workflow', 'javelin-behavior-device', 'javelin-history', 'javelin-vector', 'phabricator-shaped-request', ), 'f2441746' => array( 'javelin-dom', 'javelin-util', 'javelin-stratcom', 'javelin-install', 'javelin-request', 'javelin-workflow', ), 'f36e01af' => array( 'javelin-behavior', 'javelin-behavior-device', 'javelin-stratcom', 'javelin-vector', 'phabricator-hovercard', ), 'f3fef818' => array( 'javelin-behavior', 'javelin-dom', 'javelin-util', 'phuix-dropdown-menu', 'phuix-action-list-view', 'phuix-action-view', 'javelin-workflow', ), 'f588412e' => array( 'javelin-behavior', 'javelin-dom', 'javelin-util', 'phabricator-prefab', 'multirow-row-manager', 'javelin-json', ), 'f6555212' => array( 'javelin-install', 'javelin-reactornode', 'javelin-util', 'javelin-reactor', ), 'f726d506' => array( 'javelin-behavior', 'javelin-stratcom', 'javelin-dom', 'javelin-history', ), 'f7379f45' => array( 'javelin-behavior', 'javelin-dom', 'javelin-util', 'phabricator-shaped-request', ), 'f7f1289f' => array( 'javelin-behavior', 'javelin-dom', 'javelin-stratcom', ), 'f7fc67ec' => array( 'javelin-install', 'javelin-typeahead', 'javelin-dom', 'javelin-request', 'javelin-typeahead-ondemand-source', 'javelin-util', ), 'f8248bc5' => array( 'javelin-behavior', 'javelin-dom', 'javelin-util', 'javelin-json', 'javelin-stratcom', 'phabricator-shaped-request', ), 'f9539603' => array( 'javelin-behavior', 'javelin-dom', 'javelin-stratcom', 'javelin-uri', ), 'fa0f4fc2' => array( 'javelin-behavior', 'javelin-dom', 'javelin-vector', 'javelin-magical-init', ), 'fcba4ecc' => array( 'javelin-install', 'javelin-util', 'javelin-dom', 'javelin-typeahead-normalizer', ), 'fe9a552f' => array( 'javelin-behavior', 'multirow-row-manager', 'javelin-dom', 'javelin-util', 'phabricator-prefab', 'javelin-json', ), 42126667 => array( 'javelin-behavior', 'javelin-dom', 'javelin-request', ), 48086888 => array( 'javelin-behavior', 'javelin-dom', 'javelin-workflow', ), 60479091 => array( 'phabricator-busy', 'javelin-behavior', ), 82439934 => array( 'javelin-behavior', 'javelin-dom', 'javelin-util', 'javelin-stratcom', 'javelin-workflow', 'phabricator-draggable-list', ), ), 'packages' => array( 'core.pkg.css' => array( 'phabricator-core-css', 'phabricator-zindex-css', 'phui-button-css', 'phabricator-standard-page-view', 'aphront-dialog-view-css', 'phui-form-view-css', 'aphront-panel-view-css', 'aphront-table-view-css', 'aphront-tokenizer-control-css', 'aphront-typeahead-control-css', 'aphront-list-filter-view-css', 'phabricator-remarkup-css', 'syntax-highlighting-css', 'aphront-pager-view-css', 'phabricator-transaction-view-css', 'aphront-tooltip-css', 'phabricator-flag-css', 'aphront-error-view-css', 'sprite-gradient-css', 'sprite-menu-css', 'sprite-apps-css', 'sprite-apps-large-css', 'phabricator-main-menu-view', 'phabricator-notification-css', 'phabricator-notification-menu-css', 'lightbox-attachment-css', 'phui-header-view-css', 'phabricator-filetree-view-css', 'phabricator-nav-view-css', 'phabricator-side-menu-view-css', 'phabricator-crumbs-view-css', 'phui-object-item-list-view-css', 'global-drag-and-drop-css', 'phui-spacing-css', 'phui-form-css', 'phui-icon-view-css', 'phabricator-application-launch-view-css', 'phabricator-action-list-view-css', 'phui-property-list-view-css', 'phui-tag-view-css', 'phui-list-view-css', 'font-fontawesome', 'phui-font-icon-base-css', 'sprite-main-header-css', 'phui-box-css', 'phui-object-box-css', 'phui-timeline-view-css', 'sprite-tokens-css', 'tokens-css', 'phui-status-list-view-css', ), 'core.pkg.js' => array( 'javelin-util', 'javelin-install', 'javelin-event', 'javelin-stratcom', 'javelin-behavior', 'javelin-resource', 'javelin-request', 'javelin-vector', 'javelin-dom', 'javelin-json', 'javelin-uri', 'javelin-workflow', 'javelin-mask', 'javelin-typeahead', 'javelin-typeahead-normalizer', 'javelin-typeahead-source', 'javelin-typeahead-preloaded-source', 'javelin-typeahead-ondemand-source', 'javelin-tokenizer', 'javelin-history', 'javelin-router', 'javelin-routable', 'javelin-behavior-aphront-basic-tokenizer', 'javelin-behavior-workflow', 'javelin-behavior-aphront-form-disable-on-submit', 'phabricator-keyboard-shortcut-manager', 'phabricator-keyboard-shortcut', 'javelin-behavior-phabricator-keyboard-shortcuts', 'javelin-behavior-refresh-csrf', 'javelin-behavior-phabricator-watch-anchor', 'javelin-behavior-phabricator-autofocus', 'phuix-dropdown-menu', 'phuix-action-list-view', 'phuix-action-view', 'phabricator-phtize', 'javelin-behavior-phabricator-oncopy', 'phabricator-tooltip', 'javelin-behavior-phabricator-tooltips', 'phabricator-prefab', 'javelin-behavior-device', 'javelin-behavior-toggle-class', 'javelin-behavior-lightbox-attachments', 'phabricator-busy', 'javelin-aphlict', 'phabricator-notification', 'javelin-behavior-aphlict-listen', 'javelin-behavior-phabricator-search-typeahead', 'javelin-behavior-konami', 'javelin-behavior-aphlict-dropdown', 'javelin-behavior-history-install', 'javelin-behavior-phabricator-gesture', 'javelin-behavior-phabricator-active-nav', 'javelin-behavior-phabricator-nav', 'javelin-behavior-phabricator-remarkup-assist', 'phabricator-textareautils', 'phabricator-file-upload', 'javelin-behavior-global-drag-and-drop', 'javelin-behavior-phabricator-reveal-content', 'phabricator-hovercard', 'javelin-behavior-phabricator-hovercards', 'javelin-color', 'javelin-fx', 'phabricator-draggable-list', 'javelin-behavior-phabricator-transaction-list', 'javelin-behavior-phabricator-show-all-transactions', 'javelin-behavior-phui-timeline-dropdown-menu', 'javelin-behavior-doorkeeper-tag', ), 'darkconsole.pkg.js' => array( 'javelin-behavior-dark-console', 'javelin-behavior-error-log', ), 'differential.pkg.css' => array( 'differential-core-view-css', 'differential-changeset-view-css', 'differential-results-table-css', 'differential-revision-history-css', 'differential-revision-list-css', 'differential-table-of-contents-css', 'differential-revision-comment-css', 'differential-revision-add-comment-css', 'phabricator-object-selector-css', 'phabricator-content-source-view-css', 'inline-comment-summary-css', ), 'differential.pkg.js' => array( 'phabricator-drag-and-drop-file-upload', 'phabricator-shaped-request', 'javelin-behavior-differential-feedback-preview', 'javelin-behavior-differential-edit-inline-comments', 'javelin-behavior-differential-populate', 'javelin-behavior-differential-show-more', 'javelin-behavior-differential-diff-radios', 'javelin-behavior-differential-comment-jump', 'javelin-behavior-differential-add-reviewers-and-ccs', 'javelin-behavior-differential-keyboard-navigation', 'javelin-behavior-aphront-drag-and-drop-textarea', 'javelin-behavior-phabricator-object-selector', 'javelin-behavior-repository-crossreference', 'javelin-behavior-load-blame', 'differential-inline-comment-editor', 'javelin-behavior-differential-dropdown-menus', 'javelin-behavior-differential-toggle-files', 'javelin-behavior-differential-user-select', 'javelin-behavior-aphront-more', ), 'diffusion.pkg.css' => array( 'diffusion-icons-css', ), 'diffusion.pkg.js' => array( 'javelin-behavior-diffusion-pull-lastmodified', 'javelin-behavior-diffusion-commit-graph', 'javelin-behavior-audit-preview', ), 'maniphest.pkg.css' => array( 'maniphest-task-summary-css', ), 'maniphest.pkg.js' => array( 'javelin-behavior-maniphest-batch-selector', 'javelin-behavior-maniphest-transaction-controls', 'javelin-behavior-maniphest-transaction-preview', 'javelin-behavior-maniphest-transaction-expand', 'javelin-behavior-maniphest-subpriority-editor', 'javelin-behavior-maniphest-list-editor', ), ), ); diff --git a/src/applications/phortune/controller/PhortuneAccountViewController.php b/src/applications/phortune/controller/PhortuneAccountViewController.php index 1017f05ee1..39b23d6dba 100644 --- a/src/applications/phortune/controller/PhortuneAccountViewController.php +++ b/src/applications/phortune/controller/PhortuneAccountViewController.php @@ -1,270 +1,269 @@ accountID = $data['accountID']; } public function processRequest() { $request = $this->getRequest(); $user = $request->getUser(); $account = id(new PhortuneAccountQuery()) ->setViewer($user) ->withIDs(array($this->accountID)) ->executeOne(); if (!$account) { return new Aphront404Response(); } $title = $account->getName(); $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb(pht('Account'), $request->getRequestURI()); $header = id(new PHUIHeaderView()) ->setHeader($title); $actions = id(new PhabricatorActionListView()) ->setUser($user) ->setObjectURI($request->getRequestURI()) ->addAction( id(new PhabricatorActionView()) ->setName(pht('Edit Account')) ->setIcon('fa-pencil') ->setHref('#') ->setDisabled(true)) ->addAction( id(new PhabricatorActionView()) ->setName(pht('Edit Members')) ->setIcon('fa-users') ->setHref('#') ->setDisabled(true)); $crumbs->setActionList($actions); $properties = id(new PHUIPropertyListView()) ->setObject($account) ->setUser($user); $properties->addProperty(pht('Balance'), '-'); $properties->setActionList($actions); $payment_methods = $this->buildPaymentMethodsSection($account); $purchase_history = $this->buildPurchaseHistorySection($account); $charge_history = $this->buildChargeHistorySection($account); $account_history = $this->buildAccountHistorySection($account); $object_box = id(new PHUIObjectBoxView()) ->setHeader($header) ->addPropertyList($properties); return $this->buildApplicationPage( array( $crumbs, $object_box, $payment_methods, $purchase_history, $charge_history, $account_history, ), array( 'title' => $title, )); } private function buildPaymentMethodsSection(PhortuneAccount $account) { $request = $this->getRequest(); $viewer = $request->getUser(); $can_edit = PhabricatorPolicyFilter::hasCapability( $viewer, $account, PhabricatorPolicyCapability::CAN_EDIT); $id = $account->getID(); $header = id(new PHUIHeaderView()) ->setHeader(pht('Payment Methods')); $list = id(new PHUIObjectItemListView()) ->setUser($viewer) ->setNoDataString( pht('No payment methods associated with this account.')); $methods = id(new PhortunePaymentMethodQuery()) ->setViewer($viewer) ->withAccountPHIDs(array($account->getPHID())) ->execute(); if ($methods) { $this->loadHandles(mpull($methods, 'getAuthorPHID')); } foreach ($methods as $method) { $id = $method->getID(); $item = new PHUIObjectItemView(); $item->setHeader($method->getFullDisplayName()); switch ($method->getStatus()) { case PhortunePaymentMethod::STATUS_ACTIVE: $item->setBarColor('green'); $disable_uri = $this->getApplicationURI('card/'.$id.'/disable/'); $item->addAction( id(new PHUIListItemView()) ->setIcon('fa-times') ->setHref($disable_uri) ->setDisabled(!$can_edit) ->setWorkflow(true)); break; case PhortunePaymentMethod::STATUS_DISABLED: $item->setDisabled(true); break; } $provider = $method->buildPaymentProvider(); $item->addAttribute($provider->getPaymentMethodProviderDescription()); - $item->setImageURI($provider->getPaymentMethodIcon()); $edit_uri = $this->getApplicationURI('card/'.$id.'/edit/'); $item->addAction( id(new PHUIListItemView()) ->setIcon('fa-pencil') ->setHref($edit_uri) ->setDisabled(!$can_edit) ->setWorkflow(!$can_edit)); $list->addItem($item); } return id(new PHUIObjectBoxView()) ->setHeader($header) ->appendChild($list); } private function buildPurchaseHistorySection(PhortuneAccount $account) { $request = $this->getRequest(); $viewer = $request->getUser(); $carts = id(new PhortuneCartQuery()) ->setViewer($viewer) ->withAccountPHIDs(array($account->getPHID())) ->needPurchases(true) ->withStatuses( array( PhortuneCart::STATUS_PURCHASING, PhortuneCart::STATUS_PURCHASED, )) ->execute(); $rows = array(); $rowc = array(); foreach ($carts as $cart) { $cart_link = phutil_tag( 'a', array( 'href' => $this->getApplicationURI('cart/'.$cart->getID().'/'), ), pht('Cart %d', $cart->getID())); $rowc[] = 'highlighted'; $rows[] = array( phutil_tag('strong', array(), $cart_link), '', '', ); foreach ($cart->getPurchases() as $purchase) { $id = $purchase->getID(); $price = $purchase->getTotalPriceAsCurrency()->formatForDisplay(); $purchase_link = phutil_tag( 'a', array( 'href' => $this->getApplicationURI('purchase/'.$id.'/'), ), $purchase->getFullDisplayName()); $rowc[] = ''; $rows[] = array( '', $purchase_link, $price, ); } } $table = id(new AphrontTableView($rows)) ->setRowClasses($rowc) ->setHeaders( array( pht('Cart'), pht('Purchase'), pht('Amount'), )) ->setColumnClasses( array( '', 'wide', 'right', )); $header = id(new PHUIHeaderView()) ->setHeader(pht('Purchase History')); return id(new PHUIObjectBoxView()) ->setHeader($header) ->appendChild($table); } private function buildChargeHistorySection(PhortuneAccount $account) { $request = $this->getRequest(); $viewer = $request->getUser(); $charges = id(new PhortuneChargeQuery()) ->setViewer($viewer) ->withAccountPHIDs(array($account->getPHID())) ->needCarts(true) ->execute(); return $this->buildChargesTable($charges); } private function buildAccountHistorySection(PhortuneAccount $account) { $request = $this->getRequest(); $user = $request->getUser(); $header = id(new PHUIHeaderView()) ->setHeader(pht('Account History')); $xactions = id(new PhortuneAccountTransactionQuery()) ->setViewer($user) ->withObjectPHIDs(array($account->getPHID())) ->execute(); $engine = id(new PhabricatorMarkupEngine()) ->setViewer($user); $xaction_view = id(new PhabricatorApplicationTransactionView()) ->setUser($user) ->setObjectPHID($account->getPHID()) ->setTransactions($xactions) ->setMarkupEngine($engine); $box = id(new PHUIObjectBoxView()) ->setHeader($header); return array( $box, $xaction_view, ); } } diff --git a/src/applications/phortune/controller/PhortuneCartCheckoutController.php b/src/applications/phortune/controller/PhortuneCartCheckoutController.php index 6284c3a04b..fae092ece2 100644 --- a/src/applications/phortune/controller/PhortuneCartCheckoutController.php +++ b/src/applications/phortune/controller/PhortuneCartCheckoutController.php @@ -1,234 +1,234 @@ id = $data['id']; } public function processRequest() { $request = $this->getRequest(); $viewer = $request->getUser(); $cart = id(new PhortuneCartQuery()) ->setViewer($viewer) ->withIDs(array($this->id)) ->needPurchases(true) ->executeOne(); if (!$cart) { return new Aphront404Response(); } $cancel_uri = $cart->getCancelURI(); $merchant = $cart->getMerchant(); switch ($cart->getStatus()) { case PhortuneCart::STATUS_BUILDING: return $this->newDialog() ->setTitle(pht('Incomplete Cart')) ->appendParagraph( pht( 'The application that created this cart did not finish putting '. 'products in it. You can not checkout with an incomplete '. 'cart.')) ->addCancelButton($cancel_uri); case PhortuneCart::STATUS_READY: // This is the expected, normal state for a cart that's ready for // checkout. break; case PhortuneCart::STATUS_PURCHASING: // We've started the purchase workflow for this cart, but were not able // to complete it. If the workflow is on an external site, this could // happen because the user abandoned the workflow. Just return them to // the right place so they can resume where they left off. $uri = $cart->getMetadataValue('provider.checkoutURI'); if ($uri !== null) { return id(new AphrontRedirectResponse()) ->setIsExternal(true) ->setURI($uri); } return $this->newDialog() ->setTitle(pht('Charge Failed')) ->appendParagraph( pht( 'Failed to charge this cart.')) ->addCancelButton($cancel_uri); break; case PhortuneCart::STATUS_CHARGED: // TODO: This is really bad (we took your money and at least partially // failed to fulfill your order) and should have better steps forward. return $this->newDialog() ->setTitle(pht('Purchase Failed')) ->appendParagraph( pht( 'This cart was charged but the purchase could not be '. 'completed.')) ->addCancelButton($cancel_uri); case PhortuneCart::STATUS_PURCHASED: return id(new AphrontRedirectResponse())->setURI($cart->getDetailURI()); default: throw new Exception( pht( 'Unknown cart status "%s"!', $cart->getStatus())); } $account = $cart->getAccount(); $account_uri = $this->getApplicationURI($account->getID().'/'); $methods = id(new PhortunePaymentMethodQuery()) ->setViewer($viewer) ->withAccountPHIDs(array($account->getPHID())) ->withMerchantPHIDs(array($merchant->getPHID())) ->withStatuses(array(PhortunePaymentMethod::STATUS_ACTIVE)) ->execute(); $e_method = null; $errors = array(); if ($request->isFormPost()) { // Require CAN_EDIT on the cart to actually make purchases. PhabricatorPolicyFilter::requireCapability( $viewer, $cart, PhabricatorPolicyCapability::CAN_EDIT); $method_id = $request->getInt('paymentMethodID'); $method = idx($methods, $method_id); if (!$method) { $e_method = pht('Required'); $errors[] = pht('You must choose a payment method.'); } if (!$errors) { $provider = $method->buildPaymentProvider(); $charge = $cart->willApplyCharge($viewer, $provider, $method); $provider->applyCharge($method, $charge); $cart->didApplyCharge($charge); $done_uri = $cart->getDoneURI(); return id(new AphrontRedirectResponse())->setURI($done_uri); } } $cart_box = $this->buildCartContents($cart); $cart_box->setFormErrors($errors); $title = pht('Buy Stuff'); if (!$methods) { $method_control = id(new AphrontFormStaticControl()) ->setLabel(pht('Payment Method')) ->setValue( phutil_tag('em', array(), pht('No payment methods configured.'))); } else { $method_control = id(new AphrontFormRadioButtonControl()) ->setLabel(pht('Payment Method')) ->setName('paymentMethodID') ->setValue($request->getInt('paymentMethodID')); foreach ($methods as $method) { $method_control->addButton( $method->getID(), $method->getFullDisplayName(), $method->getDescription()); } } $method_control->setError($e_method); $account_id = $account->getID(); $payment_method_uri = $this->getApplicationURI("{$account_id}/card/new/"); $payment_method_uri = new PhutilURI($payment_method_uri); $payment_method_uri->setQueryParams( array( 'merchantID' => $merchant->getID(), 'cartID' => $cart->getID(), )); $form = id(new AphrontFormView()) ->setUser($viewer) ->appendChild($method_control); $add_providers = $this->loadCreatePaymentMethodProvidersForMerchant( $merchant); if ($add_providers) { - $new_method = phutil_tag( + $new_method = javelin_tag( 'a', array( 'class' => 'button grey', 'href' => $payment_method_uri, 'sigil' => 'workflow', ), pht('Add New Payment Method')); $form->appendChild( id(new AphrontFormMarkupControl()) ->setValue($new_method)); } if ($methods || $add_providers) { $submit = id(new AphrontFormSubmitControl()) ->setValue(pht('Submit Payment')) ->setDisabled(!$methods); if ($cart->getCancelURI() !== null) { $submit->addCancelButton($cart->getCancelURI()); } $form->appendChild($submit); } $provider_form = null; $pay_providers = $this->loadOneTimePaymentProvidersForMerchant($merchant); if ($pay_providers) { $one_time_options = array(); foreach ($pay_providers as $provider) { $one_time_options[] = $provider->renderOneTimePaymentButton( $account, $cart, $viewer); } $one_time_options = phutil_tag( 'div', array( 'class' => 'phortune-payment-onetime-list', ), $one_time_options); $provider_form = new PHUIFormLayoutView(); $provider_form->appendChild( id(new AphrontFormMarkupControl()) ->setLabel('Pay With') ->setValue($one_time_options)); } $payment_box = id(new PHUIObjectBoxView()) ->setHeaderText(pht('Choose Payment Method')) ->appendChild($form) ->appendChild($provider_form); $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb($title); return $this->buildApplicationPage( array( $crumbs, $cart_box, $payment_box, ), array( 'title' => $title, )); } } diff --git a/src/applications/phortune/controller/PhortuneController.php b/src/applications/phortune/controller/PhortuneController.php index 9195b18c10..87bd6eef7b 100644 --- a/src/applications/phortune/controller/PhortuneController.php +++ b/src/applications/phortune/controller/PhortuneController.php @@ -1,135 +1,135 @@ getRequest())); } protected function buildChargesTable(array $charges, $show_cart = true) { $request = $this->getRequest(); $viewer = $request->getUser(); $rows = array(); foreach ($charges as $charge) { $cart = $charge->getCart(); $cart_id = $cart->getID(); $cart_uri = $this->getApplicationURI("cart/{$cart_id}/"); $cart_href = phutil_tag( 'a', array( 'href' => $cart_uri, ), pht('Cart %d', $cart_id)); $rows[] = array( $charge->getID(), $cart_href, - $charge->getPaymentProviderKey(), + $charge->getProviderPHID(), $charge->getPaymentMethodPHID(), $charge->getAmountAsCurrency()->formatForDisplay(), $charge->getStatus(), phabricator_datetime($charge->getDateCreated(), $viewer), ); } $charge_table = id(new AphrontTableView($rows)) ->setHeaders( array( pht('ID'), pht('Cart'), pht('Provider'), pht('Method'), pht('Amount'), pht('Status'), pht('Created'), )) ->setColumnClasses( array( '', 'strong', '', '', 'wide right', '', '', )) ->setColumnVisibility( array( true, $show_cart, )); $header = id(new PHUIHeaderView()) ->setHeader(pht('Charge History')); return id(new PHUIObjectBoxView()) ->setHeader($header) ->appendChild($charge_table); } protected function addAccountCrumb( $crumbs, PhortuneAccount $account, $link = true) { $name = pht('Account'); $href = null; if ($link) { $href = $this->getApplicationURI($account->getID().'/'); $crumbs->addTextCrumb($name, $href); } else { $crumbs->addTextCrumb($name); } } private function loadEnabledProvidersForMerchant(PhortuneMerchant $merchant) { $viewer = $this->getRequest()->getUser(); $provider_configs = id(new PhortunePaymentProviderConfigQuery()) ->setViewer($viewer) ->withMerchantPHIDs(array($merchant->getPHID())) ->execute(); $providers = mpull($provider_configs, 'buildProvider', 'getID'); foreach ($providers as $key => $provider) { if (!$provider->isEnabled()) { unset($providers[$key]); } } return $providers; } protected function loadCreatePaymentMethodProvidersForMerchant( PhortuneMerchant $merchant) { $providers = $this->loadEnabledProvidersForMerchant($merchant); foreach ($providers as $key => $provider) { if (!$provider->canCreatePaymentMethods()) { unset($providers[$key]); continue; } } return $providers; } protected function loadOneTimePaymentProvidersForMerchant( PhortuneMerchant $merchant) { $providers = $this->loadEnabledProvidersForMerchant($merchant); foreach ($providers as $key => $provider) { if (!$provider->canProcessOneTimePayments()) { unset($providers[$key]); continue; } } return $providers; } } diff --git a/src/applications/phortune/controller/PhortunePaymentMethodCreateController.php b/src/applications/phortune/controller/PhortunePaymentMethodCreateController.php index 043ee82ff2..831f3983b0 100644 --- a/src/applications/phortune/controller/PhortunePaymentMethodCreateController.php +++ b/src/applications/phortune/controller/PhortunePaymentMethodCreateController.php @@ -1,251 +1,257 @@ accountID = $data['accountID']; } public function processRequest() { $request = $this->getRequest(); $viewer = $request->getUser(); $account = id(new PhortuneAccountQuery()) ->setViewer($viewer) ->withIDs(array($this->accountID)) ->executeOne(); if (!$account) { return new Aphront404Response(); } $merchant = id(new PhortuneMerchantQuery()) ->setViewer($viewer) ->withIDs(array($request->getInt('merchantID'))) ->executeOne(); if (!$merchant) { return new Aphront404Response(); } - $cancel_uri = $this->getApplicationURI($account->getID().'/'); - $account_uri = $this->getApplicationURI($account->getID().'/'); + $cart_id = $request->getInt('cartID'); + if ($cart_id) { + $cancel_uri = $this->getApplicationURI("cart/{$cart_id}/checkout/"); + } else { + $cancel_uri = $this->getApplicationURI($account->getID().'/'); + } $providers = $this->loadCreatePaymentMethodProvidersForMerchant($merchant); if (!$providers) { throw new Exception( 'There are no payment providers enabled that can add payment '. 'methods.'); } $provider_id = $request->getInt('providerID'); if (empty($providers[$provider_id])) { $choices = array(); foreach ($providers as $provider) { $choices[] = $this->renderSelectProvider($provider); } $content = phutil_tag( 'div', array( 'class' => 'phortune-payment-method-list', ), $choices); return $this->newDialog() ->setRenderDialogAsDiv(true) ->setTitle(pht('Add Payment Method')) ->appendParagraph(pht('Choose a payment method to add:')) ->appendChild($content) - ->addCancelButton($account_uri); + ->addCancelButton($cancel_uri); } $provider = $providers[$provider_id]; $errors = array(); if ($request->isFormPost() && $request->getBool('isProviderForm')) { $method = id(new PhortunePaymentMethod()) ->setAccountPHID($account->getPHID()) ->setAuthorPHID($viewer->getPHID()) ->setMerchantPHID($merchant->getPHID()) ->setProviderPHID($provider->getProviderConfig()->getPHID()) ->setStatus(PhortunePaymentMethod::STATUS_ACTIVE); if (!$errors) { $errors = $this->processClientErrors( $provider, $request->getStr('errors')); } if (!$errors) { $client_token_raw = $request->getStr('token'); $client_token = json_decode($client_token_raw, true); if (!is_array($client_token)) { $errors[] = pht( 'There was an error decoding token information submitted by the '. 'client. Expected a JSON-encoded token dictionary, received: %s.', nonempty($client_token_raw, pht('nothing'))); } else { if (!$provider->validateCreatePaymentMethodToken($client_token)) { $errors[] = pht( 'There was an error with the payment token submitted by the '. 'client. Expected a valid dictionary, received: %s.', $client_token_raw); } } if (!$errors) { $errors = $provider->createPaymentMethodFromRequest( $request, $method, $client_token); } } if (!$errors) { $method->save(); // If we added this method on a cart flow, return to the cart to // check out. - $cart_id = $request->getInt('cartID'); if ($cart_id) { $next_uri = $this->getApplicationURI( "cart/{$cart_id}/checkout/?paymentMethodID=".$method->getID()); } else { + $account_uri = $this->getApplicationURI($account->getID().'/'); $next_uri = new PhutilURI($account_uri); $next_uri->setFragment('payment'); } return id(new AphrontRedirectResponse())->setURI($next_uri); } else { $dialog = id(new AphrontDialogView()) ->setUser($viewer) ->setTitle(pht('Error Adding Payment Method')) ->appendChild(id(new AphrontErrorView())->setErrors($errors)) ->addCancelButton($request->getRequestURI()); return id(new AphrontDialogResponse())->setDialog($dialog); } } $form = $provider->renderCreatePaymentMethodForm($request, $errors); $form ->setUser($viewer) ->setAction($request->getRequestURI()) ->setWorkflow(true) ->addHiddenInput('providerID', $provider_id) ->addHiddenInput('cartID', $request->getInt('cartID')) ->addHiddenInput('isProviderForm', true) ->appendChild( id(new AphrontFormSubmitControl()) ->setValue(pht('Add Payment Method')) - ->addCancelButton($account_uri)); + ->addCancelButton($cancel_uri)); $box = id(new PHUIObjectBoxView()) ->setHeaderText($provider->getPaymentMethodDescription()) ->setForm($form); $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb(pht('Add Payment Method')); return $this->buildApplicationPage( array( $crumbs, $box, ), array( 'title' => $provider->getPaymentMethodDescription(), )); } private function renderSelectProvider( PhortunePaymentProvider $provider) { $request = $this->getRequest(); $viewer = $request->getUser(); $description = $provider->getPaymentMethodDescription(); $icon_uri = $provider->getPaymentMethodIcon(); $details = $provider->getPaymentMethodProviderDescription(); $this->requireResource('phortune-css'); $icon = id(new PHUIIconView()) - ->setImage($icon_uri) - ->addClass('phortune-payment-icon'); + ->setSpriteSheet(PHUIIconView::SPRITE_LOGIN) + ->setSpriteIcon($provider->getPaymentMethodIcon()); $button = id(new PHUIButtonView()) ->setSize(PHUIButtonView::BIG) ->setColor(PHUIButtonView::GREY) ->setIcon($icon) ->setText($description) - ->setSubtext($details); + ->setSubtext($details) + ->setMetadata(array('disableWorkflow' => true)); $form = id(new AphrontFormView()) ->setUser($viewer) + ->setAction($request->getRequestURI()) ->addHiddenInput('providerID', $provider->getProviderConfig()->getID()) ->appendChild($button); return $form; } private function processClientErrors( PhortunePaymentProvider $provider, $client_errors_raw) { $errors = array(); $client_errors = json_decode($client_errors_raw, true); if (!is_array($client_errors)) { $errors[] = pht( 'There was an error decoding error information submitted by the '. 'client. Expected a JSON-encoded list of error codes, received: %s.', nonempty($client_errors_raw, pht('nothing'))); } foreach (array_unique($client_errors) as $key => $client_error) { $client_errors[$key] = $provider->translateCreatePaymentMethodErrorCode( $client_error); } foreach (array_unique($client_errors) as $client_error) { switch ($client_error) { case PhortuneErrCode::ERR_CC_INVALID_NUMBER: $message = pht( 'The card number you entered is not a valid card number. Check '. 'that you entered it correctly.'); break; case PhortuneErrCode::ERR_CC_INVALID_CVC: $message = pht( 'The CVC code you entered is not a valid CVC code. Check that '. 'you entered it correctly. The CVC code is a 3-digit or 4-digit '. 'numeric code which usually appears on the back of the card.'); break; case PhortuneErrCode::ERR_CC_INVALID_EXPIRY: $message = pht( 'The card expiration date is not a valid expiration date. Check '. 'that you entered it correctly. You can not add an expired card '. 'as a payment method.'); break; default: $message = $provider->getCreatePaymentMethodErrorMessage( $client_error); if (!$message) { $message = pht( "There was an unexpected error ('%s') processing payment ". "information.", $client_error); phlog($message); } break; } $errors[$client_error] = $message; } return $errors; } } diff --git a/src/applications/phortune/provider/PhortuneBalancedPaymentProvider.php b/src/applications/phortune/provider/PhortuneBalancedPaymentProvider.php index eb9be17358..c2e74be345 100644 --- a/src/applications/phortune/provider/PhortuneBalancedPaymentProvider.php +++ b/src/applications/phortune/provider/PhortuneBalancedPaymentProvider.php @@ -1,320 +1,320 @@ getMarketplaceURI() && $this->getSecretKey(); } public function getName() { return pht('Balanced Payments'); } public function getConfigureName() { return pht('Add Balanced Payments Account'); } public function getConfigureDescription() { return pht( 'Allows you to accept credit or debit card payments with a '. 'balancedpayments.com account.'); } public function getConfigureInstructions() { return pht( "To configure Balacned, register or log in to an existing account on ". "[[https://balancedpayments.com | balancedpayments.com]]. Once logged ". "in:\n\n". " - Choose a marketplace.\n". " - Find the **Marketplace ID** in {nav My Marketplace > Settings} and ". " copy it into the field above.\n". " - On the same screen, under **API keys**, choose **Add a key**, then ". " **Show key secret**. Copy the value into the field above.\n\n". "You can either use a test marketplace to add this provider in test ". "mode, or use a live marketplace to accept live payments."); } public function getAllConfigurableProperties() { return array( self::BALANCED_MARKETPLACE_ID, self::BALANCED_SECRET_KEY, ); } public function getAllConfigurableSecretProperties() { return array( self::BALANCED_SECRET_KEY, ); } public function processEditForm( AphrontRequest $request, array $values) { $errors = array(); $issues = array(); if (!strlen($values[self::BALANCED_MARKETPLACE_ID])) { $errors[] = pht('Balanced Marketplace ID is required.'); $issues[self::BALANCED_MARKETPLACE_ID] = pht('Required'); } if (!strlen($values[self::BALANCED_SECRET_KEY])) { $errors[] = pht('Balanced Secret Key is required.'); $issues[self::BALANCED_SECRET_KEY] = pht('Required'); } return array($errors, $issues, $values); } public function extendEditForm( AphrontRequest $request, AphrontFormView $form, array $values, array $issues) { $form ->appendChild( id(new AphrontFormTextControl()) ->setName(self::BALANCED_MARKETPLACE_ID) ->setValue($values[self::BALANCED_MARKETPLACE_ID]) ->setError(idx($issues, self::BALANCED_MARKETPLACE_ID, true)) ->setLabel(pht('Balanced Marketplace ID'))) ->appendChild( id(new AphrontFormTextControl()) ->setName(self::BALANCED_SECRET_KEY) ->setValue($values[self::BALANCED_SECRET_KEY]) ->setError(idx($issues, self::BALANCED_SECRET_KEY, true)) ->setLabel(pht('Balanced Secret Key'))); } public function canRunConfigurationTest() { return true; } public function runConfigurationTest() { $root = dirname(phutil_get_library_root('phabricator')); require_once $root.'/externals/httpful/bootstrap.php'; require_once $root.'/externals/restful/bootstrap.php'; require_once $root.'/externals/balanced-php/bootstrap.php'; // TODO: This only tests that the secret key is correct. It's not clear // how to test that the marketplace is correct. try { Balanced\Settings::$api_key = $this->getSecretKey(); Balanced\APIKey::query()->first(); } catch (RESTful\Exceptions\HTTPError $error) { // NOTE: This exception doesn't print anything meaningful if it escapes // to top level. Replace it with something slightly readable. throw new Exception($error->response->body->description); } } public function getPaymentMethodDescription() { return pht('Add Credit or Debit Card'); } public function getPaymentMethodIcon() { - return celerity_get_resource_uri('/rsrc/image/phortune/balanced.png'); + return 'Balanced'; } public function getPaymentMethodProviderDescription() { return pht('Processed by Balanced'); } public function getDefaultPaymentMethodDisplayName( PhortunePaymentMethod $method) { return pht('Credit/Debit Card'); } protected function executeCharge( PhortunePaymentMethod $method, PhortuneCharge $charge) { $root = dirname(phutil_get_library_root('phabricator')); require_once $root.'/externals/httpful/bootstrap.php'; require_once $root.'/externals/restful/bootstrap.php'; require_once $root.'/externals/balanced-php/bootstrap.php'; $price = $charge->getAmountAsCurrency(); // Build the string which will appear on the credit card statement. $charge_as = new PhutilURI(PhabricatorEnv::getProductionURI('/')); $charge_as = $charge_as->getDomain(); $charge_as = id(new PhutilUTF8StringTruncator()) ->setMaximumBytes(22) ->setTerminator('') ->truncateString($charge_as); try { Balanced\Settings::$api_key = $this->getSecretKey(); $card = Balanced\Card::get($method->getMetadataValue('balanced.cardURI')); $debit = $card->debit($price->getValueInUSDCents(), $charge_as); } catch (RESTful\Exceptions\HTTPError $error) { // NOTE: This exception doesn't print anything meaningful if it escapes // to top level. Replace it with something slightly readable. throw new Exception($error->response->body->description); } $expect_status = 'succeeded'; if ($debit->status !== $expect_status) { throw new Exception( pht( 'Debit failed, expected "%s", got "%s".', $expect_status, $debit->status)); } $charge->setMetadataValue('balanced.debitURI', $debit->uri); $charge->save(); } private function getMarketplaceID() { return $this ->getProviderConfig() ->getMetadataValue(self::BALANCED_MARKETPLACE_ID); } private function getSecretKey() { return $this ->getProviderConfig() ->getMetadataValue(self::BALANCED_SECRET_KEY); } private function getMarketplaceURI() { return '/v1/marketplace/'.$this->getMarketplaceID(); } /* -( Adding Payment Methods )--------------------------------------------- */ public function canCreatePaymentMethods() { return true; } public function validateCreatePaymentMethodToken(array $token) { return isset($token['balancedMarketplaceURI']); } /** * @phutil-external-symbol class Balanced\Card * @phutil-external-symbol class Balanced\Settings * @phutil-external-symbol class Balanced\Marketplace * @phutil-external-symbol class Balanced\APIKey * @phutil-external-symbol class RESTful\Exceptions\HTTPError */ public function createPaymentMethodFromRequest( AphrontRequest $request, PhortunePaymentMethod $method, array $token) { $errors = array(); $root = dirname(phutil_get_library_root('phabricator')); require_once $root.'/externals/httpful/bootstrap.php'; require_once $root.'/externals/restful/bootstrap.php'; require_once $root.'/externals/balanced-php/bootstrap.php'; $account_phid = $method->getAccountPHID(); $author_phid = $method->getAuthorPHID(); $description = $account_phid.':'.$author_phid; try { Balanced\Settings::$api_key = $this->getSecretKey(); $card = Balanced\Card::get($token['balancedMarketplaceURI']); $buyer = Balanced\Marketplace::mine()->createBuyer( null, $card->uri, array( 'description' => $description, )); } catch (RESTful\Exceptions\HTTPError $error) { // NOTE: This exception doesn't print anything meaningful if it escapes // to top level. Replace it with something slightly readable. throw new Exception($error->response->body->description); } $method ->setBrand($card->brand) ->setLastFourDigits($card->last_four) ->setExpires($card->expiration_year, $card->expiration_month) ->setMetadata( array( 'type' => 'balanced.account', 'balanced.accountURI' => $buyer->uri, 'balanced.cardURI' => $card->uri, )); return $errors; } public function renderCreatePaymentMethodForm( AphrontRequest $request, array $errors) { $ccform = id(new PhortuneCreditCardForm()) ->setUser($request->getUser()) ->setErrors($errors) ->addScript('https://js.balancedpayments.com/v1/balanced.js'); Javelin::initBehavior( 'balanced-payment-form', array( 'balancedMarketplaceURI' => $this->getMarketplaceURI(), 'formID' => $ccform->getFormID(), )); return $ccform->buildForm(); } private function getBalancedShortErrorCode($error_code) { $prefix = 'cc:balanced:'; if (strncmp($error_code, $prefix, strlen($prefix))) { return null; } return substr($error_code, strlen($prefix)); } public function translateCreatePaymentMethodErrorCode($error_code) { $short_code = $this->getBalancedShortErrorCode($error_code); if ($short_code) { static $map = array( ); if (isset($map[$short_code])) { return $map[$short_code]; } } return $error_code; } public function getCreatePaymentMethodErrorMessage($error_code) { $short_code = $this->getBalancedShortErrorCode($error_code); if (!$short_code) { return null; } switch ($short_code) { default: break; } return null; } } diff --git a/src/applications/phortune/provider/PhortunePayPalPaymentProvider.php b/src/applications/phortune/provider/PhortunePayPalPaymentProvider.php index c1632c52e1..91d92915a5 100644 --- a/src/applications/phortune/provider/PhortunePayPalPaymentProvider.php +++ b/src/applications/phortune/provider/PhortunePayPalPaymentProvider.php @@ -1,357 +1,357 @@ getPaypalAPIUsername() && $this->getPaypalAPIPassword() && $this->getPaypalAPISignature(); } public function getName() { return pht('PayPal'); } public function getConfigureName() { return pht('Add PayPal Payments Account'); } public function getConfigureDescription() { return pht( 'Allows you to accept various payment instruments with a paypal.com '. 'account.'); } public function getConfigureInstructions() { return pht( "To configure PayPal, register or log into an existing account on ". "[[https://paypal.com | paypal.com]] (for live payments) or ". "[[https://sandbox.paypal.com | sandbox.paypal.com]] (for test ". "payments). Once logged in:\n\n". " - Navigate to {nav Tools > API Access}.\n". " - Choose **View API Signature**.\n". " - Copy the **API Username**, **API Password** and **Signature** ". " into the fields above.\n\n". "You can select whether the provider operates in test mode or ". "accepts live payments using the **Mode** dropdown above.\n\n". "You can either use `sandbox.paypal.com` to retrieve live credentials, ". "or `paypal.com` to retrieve live credentials."); } public function getAllConfigurableProperties() { return array( self::PAYPAL_API_USERNAME, self::PAYPAL_API_PASSWORD, self::PAYPAL_API_SIGNATURE, self::PAYPAL_MODE, ); } public function getAllConfigurableSecretProperties() { return array( self::PAYPAL_API_PASSWORD, self::PAYPAL_API_SIGNATURE, ); } public function processEditForm( AphrontRequest $request, array $values) { $errors = array(); $issues = array(); if (!strlen($values[self::PAYPAL_API_USERNAME])) { $errors[] = pht('PayPal API Username is required.'); $issues[self::PAYPAL_API_USERNAME] = pht('Required'); } if (!strlen($values[self::PAYPAL_API_PASSWORD])) { $errors[] = pht('PayPal API Password is required.'); $issues[self::PAYPAL_API_PASSWORD] = pht('Required'); } if (!strlen($values[self::PAYPAL_API_SIGNATURE])) { $errors[] = pht('PayPal API Signature is required.'); $issues[self::PAYPAL_API_SIGNATURE] = pht('Required'); } if (!strlen($values[self::PAYPAL_MODE])) { $errors[] = pht('Mode is required.'); $issues[self::PAYPAL_MODE] = pht('Required'); } return array($errors, $issues, $values); } public function extendEditForm( AphrontRequest $request, AphrontFormView $form, array $values, array $issues) { $form ->appendChild( id(new AphrontFormTextControl()) ->setName(self::PAYPAL_API_USERNAME) ->setValue($values[self::PAYPAL_API_USERNAME]) ->setError(idx($issues, self::PAYPAL_API_USERNAME, true)) ->setLabel(pht('Paypal API Username'))) ->appendChild( id(new AphrontFormTextControl()) ->setName(self::PAYPAL_API_PASSWORD) ->setValue($values[self::PAYPAL_API_PASSWORD]) ->setError(idx($issues, self::PAYPAL_API_PASSWORD, true)) ->setLabel(pht('Paypal API Password'))) ->appendChild( id(new AphrontFormTextControl()) ->setName(self::PAYPAL_API_SIGNATURE) ->setValue($values[self::PAYPAL_API_SIGNATURE]) ->setError(idx($issues, self::PAYPAL_API_SIGNATURE, true)) ->setLabel(pht('Paypal API Signature'))) ->appendChild( id(new AphrontFormSelectControl()) ->setName(self::PAYPAL_MODE) ->setValue($values[self::PAYPAL_MODE]) ->setError(idx($issues, self::PAYPAL_MODE)) ->setLabel(pht('Mode')) ->setOptions( array( 'test' => pht('Test Mode'), 'live' => pht('Live Mode'), ))); return; } public function canRunConfigurationTest() { return true; } public function runConfigurationTest() { $result = $this ->newPaypalAPICall() ->setRawPayPalQuery('GetBalance', array()) ->resolve(); } public function getPaymentMethodDescription() { return pht('Credit Card or PayPal Account'); } public function getPaymentMethodIcon() { - return celerity_get_resource_uri('rsrc/image/phortune/paypal.png'); + return 'PayPal'; } public function getPaymentMethodProviderDescription() { return 'PayPal'; } protected function executeCharge( PhortunePaymentMethod $payment_method, PhortuneCharge $charge) { throw new Exception('!'); } private function getPaypalAPIUsername() { return $this ->getProviderConfig() ->getMetadataValue(self::PAYPAL_API_USERNAME); } private function getPaypalAPIPassword() { return $this ->getProviderConfig() ->getMetadataValue(self::PAYPAL_API_PASSWORD); } private function getPaypalAPISignature() { return $this ->getProviderConfig() ->getMetadataValue(self::PAYPAL_API_SIGNATURE); } /* -( One-Time Payments )-------------------------------------------------- */ public function canProcessOneTimePayments() { return true; } /* -( Controllers )-------------------------------------------------------- */ public function canRespondToControllerAction($action) { switch ($action) { case 'checkout': case 'charge': case 'cancel': return true; } return parent::canRespondToControllerAction(); } public function processControllerRequest( PhortuneProviderActionController $controller, AphrontRequest $request) { $viewer = $request->getUser(); $cart = $controller->loadCart($request->getInt('cartID')); if (!$cart) { return new Aphront404Response(); } $charge = $controller->loadActiveCharge($cart); switch ($controller->getAction()) { case 'checkout': if ($charge) { throw new Exception(pht('Cart is already charging!')); } break; case 'charge': case 'cancel': if (!$charge) { throw new Exception(pht('Cart is not charging yet!')); } break; } switch ($controller->getAction()) { case 'checkout': $return_uri = $this->getControllerURI( 'charge', array( 'cartID' => $cart->getID(), )); $cancel_uri = $this->getControllerURI( 'cancel', array( 'cartID' => $cart->getID(), )); $price = $cart->getTotalPriceAsCurrency(); $charge = $cart->willApplyCharge($viewer, $this); $params = array( 'PAYMENTREQUEST_0_AMT' => $price->formatBareValue(), 'PAYMENTREQUEST_0_CURRENCYCODE' => $price->getCurrency(), 'PAYMENTREQUEST_0_PAYMENTACTION' => 'Sale', 'PAYMENTREQUEST_0_CUSTOM' => $charge->getPHID(), 'RETURNURL' => $return_uri, 'CANCELURL' => $cancel_uri, // TODO: This should be cart-dependent if we eventually support // physical goods. 'NOSHIPPING' => '1', ); $result = $this ->newPaypalAPICall() ->setRawPayPalQuery('SetExpressCheckout', $params) ->resolve(); $uri = new PhutilURI('https://www.sandbox.paypal.com/cgi-bin/webscr'); $uri->setQueryParams( array( 'cmd' => '_express-checkout', 'token' => $result['TOKEN'], )); $cart->setMetadataValue('provider.checkoutURI', $uri); $cart->save(); $charge->setMetadataValue('paypal.token', $result['TOKEN']); $charge->save(); return id(new AphrontRedirectResponse()) ->setIsExternal(true) ->setURI($uri); case 'charge': $token = $request->getStr('token'); $params = array( 'TOKEN' => $token, ); $result = $this ->newPaypalAPICall() ->setRawPayPalQuery('GetExpressCheckoutDetails', $params) ->resolve(); var_dump($result); if ($result['CUSTOM'] !== $charge->getPHID()) { throw new Exception( pht('Paypal checkout does not match Phortune charge!')); } if ($result['CHECKOUTSTATUS'] !== 'PaymentActionNotInitiated') { throw new Exception( pht( 'Expected status "%s", got "%s".', 'PaymentActionNotInitiated', $result['CHECKOUTSTATUS'])); } $price = $cart->getTotalPriceAsCurrency(); $params = array( 'TOKEN' => $token, 'PAYERID' => $result['PAYERID'], 'PAYMENTREQUEST_0_AMT' => $price->formatBareValue(), 'PAYMENTREQUEST_0_CURRENCYCODE' => $price->getCurrency(), 'PAYMENTREQUEST_0_PAYMENTACTION' => 'Sale', ); $result = $this ->newPaypalAPICall() ->setRawPayPalQuery('DoExpressCheckoutPayment', $params) ->resolve(); // TODO: Paypal can send requests back in "PaymentReview" status, // and does this for test transactions. We're supposed to hold // the transaction and poll the API every 6 hours. This is unreasonably // difficult for now and we can't reasonably just fail these charges. var_dump($result); die(); break; case 'cancel': var_dump($_REQUEST); break; } throw new Exception( pht('Unsupported action "%s".', $controller->getAction())); } private function newPaypalAPICall() { $mode = $this->getProviderConfig()->getMetadataValue(self::PAYPAL_MODE); if ($mode == 'live') { $host = 'https://api-3t.paypal.com/nvp'; } else { $host = 'https://api-3t.sandbox.paypal.com/nvp'; } return id(new PhutilPayPalAPIFuture()) ->setHost($host) ->setAPIUsername($this->getPaypalAPIUsername()) ->setAPIPassword($this->getPaypalAPIPassword()) ->setAPISignature($this->getPaypalAPISignature()); } } diff --git a/src/applications/phortune/provider/PhortunePaymentProvider.php b/src/applications/phortune/provider/PhortunePaymentProvider.php index 3874752ede..a6e925cb1e 100644 --- a/src/applications/phortune/provider/PhortunePaymentProvider.php +++ b/src/applications/phortune/provider/PhortunePaymentProvider.php @@ -1,280 +1,279 @@ providerConfig = $provider_config; return $this; } public function getProviderConfig() { return $this->providerConfig; } /** * Return a short name which identifies this provider. */ abstract public function getName(); /* -( Configuring Providers )---------------------------------------------- */ /** * Return a human-readable provider name for use on the merchant workflow * where a merchant owner adds providers. */ abstract public function getConfigureName(); /** * Return a human-readable provider description for use on the merchant * workflow where a merchant owner adds providers. */ abstract public function getConfigureDescription(); abstract public function getConfigureInstructions(); abstract public function getAllConfigurableProperties(); abstract public function getAllConfigurableSecretProperties(); /** * Read a dictionary of properties from the provider's configuration for * use when editing the provider. */ public function readEditFormValuesFromProviderConfig() { $properties = $this->getAllConfigurableProperties(); $config = $this->getProviderConfig(); $secrets = $this->getAllConfigurableSecretProperties(); $secrets = array_fuse($secrets); $map = array(); foreach ($properties as $property) { $map[$property] = $config->getMetadataValue($property); if (isset($secrets[$property])) { $map[$property] = $this->renderConfigurationSecret($map[$property]); } } return $map; } /** * Read a dictionary of properties from a request for use when editing the * provider. */ public function readEditFormValuesFromRequest(AphrontRequest $request) { $properties = $this->getAllConfigurableProperties(); $map = array(); foreach ($properties as $property) { $map[$property] = $request->getStr($property); } return $map; } abstract public function processEditForm( AphrontRequest $request, array $values); abstract public function extendEditForm( AphrontRequest $request, AphrontFormView $form, array $values, array $issues); protected function renderConfigurationSecret($value) { if (strlen($value)) { return str_repeat('*', strlen($value)); } return ''; } public function isConfigurationSecret($value) { return preg_match('/^\*+\z/', trim($value)); } abstract public function canRunConfigurationTest(); public function runConfigurationTest() { throw new PhortuneNotImplementedException($this); } /* -( Selecting Providers )------------------------------------------------ */ public static function getAllProviders() { return id(new PhutilSymbolLoader()) ->setAncestorClass('PhortunePaymentProvider') ->loadObjects(); } abstract public function isEnabled(); abstract public function getPaymentMethodDescription(); abstract public function getPaymentMethodIcon(); abstract public function getPaymentMethodProviderDescription(); final public function applyCharge( PhortunePaymentMethod $payment_method, PhortuneCharge $charge) { $this->executeCharge($payment_method, $charge); } abstract protected function executeCharge( PhortunePaymentMethod $payment_method, PhortuneCharge $charge); /* -( Adding Payment Methods )--------------------------------------------- */ /** * @task addmethod */ public function canCreatePaymentMethods() { return false; } /** * @task addmethod */ public function translateCreatePaymentMethodErrorCode($error_code) { throw new PhortuneNotImplementedException($this); } /** * @task addmethod */ public function getCreatePaymentMethodErrorMessage($error_code) { throw new PhortuneNotImplementedException($this); } /** * @task addmethod */ public function validateCreatePaymentMethodToken(array $token) { throw new PhortuneNotImplementedException($this); } /** * @task addmethod */ public function createPaymentMethodFromRequest( AphrontRequest $request, PhortunePaymentMethod $method, array $token) { throw new PhortuneNotImplementedException($this); } /** * @task addmethod */ public function renderCreatePaymentMethodForm( AphrontRequest $request, array $errors) { throw new PhortuneNotImplementedException($this); } public function getDefaultPaymentMethodDisplayName( PhortunePaymentMethod $method) { throw new PhortuneNotImplementedException($this); } /* -( One-Time Payments )-------------------------------------------------- */ public function canProcessOneTimePayments() { return false; } public function renderOneTimePaymentButton( PhortuneAccount $account, PhortuneCart $cart, PhabricatorUser $user) { require_celerity_resource('phortune-css'); - $icon_uri = $this->getPaymentMethodIcon(); $description = $this->getPaymentMethodProviderDescription(); $details = $this->getPaymentMethodDescription(); $icon = id(new PHUIIconView()) - ->setImage($icon_uri) - ->addClass('phortune-payment-icon'); + ->setSpriteSheet(PHUIIconView::SPRITE_LOGIN) + ->setSpriteIcon($this->getPaymentMethodIcon()); $button = id(new PHUIButtonView()) ->setSize(PHUIButtonView::BIG) ->setColor(PHUIButtonView::GREY) ->setIcon($icon) ->setText($description) ->setSubtext($details); // NOTE: We generate a local URI to make sure the form picks up CSRF tokens. $uri = $this->getControllerURI( 'checkout', array( 'cartID' => $cart->getID(), ), $local = true); return phabricator_form( $user, array( 'action' => $uri, 'method' => 'POST', ), $button); } /* -( Controllers )-------------------------------------------------------- */ final public function getControllerURI( $action, array $params = array(), $local = false) { $id = $this->getProviderConfig()->getID(); $app = PhabricatorApplication::getByClass('PhabricatorPhortuneApplication'); $path = $app->getBaseURI().'provider/'.$id.'/'.$action.'/'; $uri = new PhutilURI($path); $uri->setQueryParams($params); if ($local) { return $uri; } else { return PhabricatorEnv::getURI((string)$uri); } } public function canRespondToControllerAction($action) { return false; } public function processControllerRequest( PhortuneProviderActionController $controller, AphrontRequest $request) { throw new PhortuneNotImplementedException($this); } } diff --git a/src/applications/phortune/provider/PhortuneStripePaymentProvider.php b/src/applications/phortune/provider/PhortuneStripePaymentProvider.php index e50f6a6a57..6cef2cbf3e 100644 --- a/src/applications/phortune/provider/PhortuneStripePaymentProvider.php +++ b/src/applications/phortune/provider/PhortuneStripePaymentProvider.php @@ -1,331 +1,331 @@ getPublishableKey() && $this->getSecretKey(); } public function getName() { return pht('Stripe'); } public function getConfigureName() { return pht('Add Stripe Payments Account'); } public function getConfigureDescription() { return pht( 'Allows you to accept credit or debit card payments with a '. 'stripe.com account.'); } public function getPaymentMethodDescription() { return pht('Add Credit or Debit Card (US and Canada)'); } public function getPaymentMethodIcon() { - return celerity_get_resource_uri('/rsrc/image/phortune/stripe.png'); + return 'Stripe'; } public function getPaymentMethodProviderDescription() { return pht('Processed by Stripe'); } public function getDefaultPaymentMethodDisplayName( PhortunePaymentMethod $method) { return pht('Credit/Debit Card'); } public function getAllConfigurableProperties() { return array( self::STRIPE_PUBLISHABLE_KEY, self::STRIPE_SECRET_KEY, ); } public function getAllConfigurableSecretProperties() { return array( self::STRIPE_SECRET_KEY, ); } public function processEditForm( AphrontRequest $request, array $values) { $errors = array(); $issues = array(); if (!strlen($values[self::STRIPE_SECRET_KEY])) { $errors[] = pht('Stripe Secret Key is required.'); $issues[self::STRIPE_SECRET_KEY] = pht('Required'); } if (!strlen($values[self::STRIPE_PUBLISHABLE_KEY])) { $errors[] = pht('Stripe Publishable Key is required.'); $issues[self::STRIPE_PUBLISHABLE_KEY] = pht('Required'); } return array($errors, $issues, $values); } public function extendEditForm( AphrontRequest $request, AphrontFormView $form, array $values, array $issues) { $form ->appendChild( id(new AphrontFormTextControl()) ->setName(self::STRIPE_SECRET_KEY) ->setValue($values[self::STRIPE_SECRET_KEY]) ->setError(idx($issues, self::STRIPE_SECRET_KEY, true)) ->setLabel(pht('Stripe Secret Key'))) ->appendChild( id(new AphrontFormTextControl()) ->setName(self::STRIPE_PUBLISHABLE_KEY) ->setValue($values[self::STRIPE_PUBLISHABLE_KEY]) ->setError(idx($issues, self::STRIPE_PUBLISHABLE_KEY, true)) ->setLabel(pht('Stripe Publishable Key'))); } public function getConfigureInstructions() { return pht( "To configure Stripe, register or log in to an existing account on ". "[[https://stripe.com | stripe.com]]. Once logged in:\n\n". " - Go to {nav icon=user, name=Your Account > Account Settings ". "> API Keys}\n". " - Copy the **Secret Key** and **Publishable Key** into the fields ". "above.\n\n". "You can either use the test keys to add this provider in test mode, ". "or the live keys to accept live payments."); } public function canRunConfigurationTest() { return true; } public function runConfigurationTest() { $root = dirname(phutil_get_library_root('phabricator')); require_once $root.'/externals/stripe-php/lib/Stripe.php'; $secret_key = $this->getSecretKey(); $account = Stripe_Account::retrieve($secret_key); } /** * @phutil-external-symbol class Stripe_Charge * @phutil-external-symbol class Stripe_CardError * @phutil-external-symbol class Stripe_Account */ protected function executeCharge( PhortunePaymentMethod $method, PhortuneCharge $charge) { $root = dirname(phutil_get_library_root('phabricator')); require_once $root.'/externals/stripe-php/lib/Stripe.php'; $price = $charge->getAmountAsCurrency(); $secret_key = $this->getSecretKey(); $params = array( 'amount' => $price->getValueInUSDCents(), 'currency' => $price->getCurrency(), 'customer' => $method->getMetadataValue('stripe.customerID'), 'description' => $charge->getPHID(), 'capture' => true, ); try { $stripe_charge = Stripe_Charge::create($params, $secret_key); } catch (Stripe_CardError $ex) { // TODO: Fail charge explicitly. throw $ex; } $id = $stripe_charge->id; if (!$id) { throw new Exception('Stripe charge call did not return an ID!'); } $charge->setMetadataValue('stripe.chargeID', $id); $charge->save(); } private function getPublishableKey() { return $this ->getProviderConfig() ->getMetadataValue(self::STRIPE_PUBLISHABLE_KEY); } private function getSecretKey() { return $this ->getProviderConfig() ->getMetadataValue(self::STRIPE_SECRET_KEY); } /* -( Adding Payment Methods )--------------------------------------------- */ public function canCreatePaymentMethods() { return true; } /** * @phutil-external-symbol class Stripe_Token * @phutil-external-symbol class Stripe_Customer */ public function createPaymentMethodFromRequest( AphrontRequest $request, PhortunePaymentMethod $method, array $token) { $errors = array(); $root = dirname(phutil_get_library_root('phabricator')); require_once $root.'/externals/stripe-php/lib/Stripe.php'; $secret_key = $this->getSecretKey(); $stripe_token = $token['stripeCardToken']; // First, make sure the token is valid. $info = id(new Stripe_Token())->retrieve($stripe_token, $secret_key); $account_phid = $method->getAccountPHID(); $author_phid = $method->getAuthorPHID(); $params = array( 'card' => $stripe_token, 'description' => $account_phid.':'.$author_phid, ); // Then, we need to create a Customer in order to be able to charge // the card more than once. We create one Customer for each card; // they do not map to PhortuneAccounts because we allow an account to // have more than one active card. $customer = Stripe_Customer::create($params, $secret_key); $card = $info->card; $method ->setBrand($card->brand) ->setLastFourDigits($card->last4) ->setExpires($card->exp_year, $card->exp_month) ->setMetadata( array( 'type' => 'stripe.customer', 'stripe.customerID' => $customer->id, 'stripe.cardToken' => $stripe_token, )); return $errors; } public function renderCreatePaymentMethodForm( AphrontRequest $request, array $errors) { $ccform = id(new PhortuneCreditCardForm()) ->setUser($request->getUser()) ->setErrors($errors) ->addScript('https://js.stripe.com/v2/'); Javelin::initBehavior( 'stripe-payment-form', array( 'stripePublishableKey' => $this->getPublishableKey(), 'formID' => $ccform->getFormID(), )); return $ccform->buildForm(); } private function getStripeShortErrorCode($error_code) { $prefix = 'cc:stripe:'; if (strncmp($error_code, $prefix, strlen($prefix))) { return null; } return substr($error_code, strlen($prefix)); } public function validateCreatePaymentMethodToken(array $token) { return isset($token['stripeCardToken']); } public function translateCreatePaymentMethodErrorCode($error_code) { $short_code = $this->getStripeShortErrorCode($error_code); if ($short_code) { static $map = array( 'error:invalid_number' => PhortuneErrCode::ERR_CC_INVALID_NUMBER, 'error:invalid_cvc' => PhortuneErrCode::ERR_CC_INVALID_CVC, 'error:invalid_expiry_month' => PhortuneErrCode::ERR_CC_INVALID_EXPIRY, 'error:invalid_expiry_year' => PhortuneErrCode::ERR_CC_INVALID_EXPIRY, ); if (isset($map[$short_code])) { return $map[$short_code]; } } return $error_code; } /** * See https://stripe.com/docs/api#errors for more information on possible * errors. */ public function getCreatePaymentMethodErrorMessage($error_code) { $short_code = $this->getStripeShortErrorCode($error_code); if (!$short_code) { return null; } switch ($short_code) { case 'error:incorrect_number': $error_key = 'number'; $message = pht('Invalid or incorrect credit card number.'); break; case 'error:incorrect_cvc': $error_key = 'cvc'; $message = pht('Card CVC is invalid or incorrect.'); break; $error_key = 'exp'; $message = pht('Card expiration date is invalid or incorrect.'); break; case 'error:invalid_expiry_month': case 'error:invalid_expiry_year': case 'error:invalid_cvc': case 'error:invalid_number': // NOTE: These should be translated into Phortune error codes earlier, // so we don't expect to receive them here. They are listed for clarity // and completeness. If we encounter one, we treat it as an unknown // error. break; case 'error:invalid_amount': case 'error:missing': case 'error:card_declined': case 'error:expired_card': case 'error:duplicate_transaction': case 'error:processing_error': default: // NOTE: These errors currently don't recevive a detailed message. // NOTE: We can also end up here with "http:nnn" messages. // TODO: At least some of these should have a better message, or be // translated into common errors above. break; } return null; } } diff --git a/src/applications/phortune/provider/PhortuneTestPaymentProvider.php b/src/applications/phortune/provider/PhortuneTestPaymentProvider.php index cbff927fb7..4750931b71 100644 --- a/src/applications/phortune/provider/PhortuneTestPaymentProvider.php +++ b/src/applications/phortune/provider/PhortuneTestPaymentProvider.php @@ -1,144 +1,144 @@ setExpires('2050', '01') ->setBrand('FreeMoney') ->setLastFourDigits('9999') ->setMetadata( array( 'type' => 'test.wealth', )); return array(); } /** * @task addmethod */ public function renderCreatePaymentMethodForm( AphrontRequest $request, array $errors) { $ccform = id(new PhortuneCreditCardForm()) ->setUser($request->getUser()) ->setErrors($errors); Javelin::initBehavior( 'test-payment-form', array( 'formID' => $ccform->getFormID(), )); return $ccform->buildForm(); } } diff --git a/src/applications/phortune/provider/PhortuneWePayPaymentProvider.php b/src/applications/phortune/provider/PhortuneWePayPaymentProvider.php index 7dacd7ed32..9f22c76632 100644 --- a/src/applications/phortune/provider/PhortuneWePayPaymentProvider.php +++ b/src/applications/phortune/provider/PhortuneWePayPaymentProvider.php @@ -1,343 +1,343 @@ getWePayClientID() && $this->getWePayClientSecret() && $this->getWePayAccessToken() && $this->getWePayAccountID(); } public function getName() { return pht('WePay'); } public function getConfigureName() { return pht('Add WePay Payments Account'); } public function getConfigureDescription() { return pht( 'Allows you to accept credit or debit card payments with a '. 'wepay.com account.'); } public function getConfigureInstructions() { return pht( "To configure WePay, register or log in to an existing account on ". "[[https://wepay.com | wepay.com]] (for live payments) or ". "[[https://stage.wepay.com | stage.wepay.com]] (for testing). ". "Once logged in:\n\n". " - Create an API application if you don't already have one.\n". " - Click the API application name to go to the detail page.\n". " - Copy **Client ID**, **Client Secret**, **Access Token** and ". " **AccountID** from that page to the fields above.\n\n". "You can either use `stage.wepay.com` to retrieve test credentials, ". "or `wepay.com` to retrieve live credentials for accepting live ". "payments."); } public function canRunConfigurationTest() { return true; } public function runConfigurationTest() { $root = dirname(phutil_get_library_root('phabricator')); require_once $root.'/externals/wepay/wepay.php'; WePay::useStaging( $this->getWePayClientID(), $this->getWePayClientSecret()); $wepay = new WePay($this->getWePayAccessToken()); $params = array( 'client_id' => $this->getWePayClientID(), 'client_secret' => $this->getWePayClientSecret(), ); $wepay->request('app', $params); } public function getAllConfigurableProperties() { return array( self::WEPAY_CLIENT_ID, self::WEPAY_CLIENT_SECRET, self::WEPAY_ACCESS_TOKEN, self::WEPAY_ACCOUNT_ID, ); } public function getAllConfigurableSecretProperties() { return array( self::WEPAY_CLIENT_SECRET, ); } public function processEditForm( AphrontRequest $request, array $values) { $errors = array(); $issues = array(); if (!strlen($values[self::WEPAY_CLIENT_ID])) { $errors[] = pht('WePay Client ID is required.'); $issues[self::WEPAY_CLIENT_ID] = pht('Required'); } if (!strlen($values[self::WEPAY_CLIENT_SECRET])) { $errors[] = pht('WePay Client Secret is required.'); $issues[self::WEPAY_CLIENT_SECRET] = pht('Required'); } if (!strlen($values[self::WEPAY_ACCESS_TOKEN])) { $errors[] = pht('WePay Access Token is required.'); $issues[self::WEPAY_ACCESS_TOKEN] = pht('Required'); } if (!strlen($values[self::WEPAY_ACCOUNT_ID])) { $errors[] = pht('WePay Account ID is required.'); $issues[self::WEPAY_ACCOUNT_ID] = pht('Required'); } return array($errors, $issues, $values); } public function extendEditForm( AphrontRequest $request, AphrontFormView $form, array $values, array $issues) { $form ->appendChild( id(new AphrontFormTextControl()) ->setName(self::WEPAY_CLIENT_ID) ->setValue($values[self::WEPAY_CLIENT_ID]) ->setError(idx($issues, self::WEPAY_CLIENT_ID, true)) ->setLabel(pht('WePay Client ID'))) ->appendChild( id(new AphrontFormTextControl()) ->setName(self::WEPAY_CLIENT_SECRET) ->setValue($values[self::WEPAY_CLIENT_SECRET]) ->setError(idx($issues, self::WEPAY_CLIENT_SECRET, true)) ->setLabel(pht('WePay Client Secret'))) ->appendChild( id(new AphrontFormTextControl()) ->setName(self::WEPAY_ACCESS_TOKEN) ->setValue($values[self::WEPAY_ACCESS_TOKEN]) ->setError(idx($issues, self::WEPAY_ACCESS_TOKEN, true)) ->setLabel(pht('WePay Access Token'))) ->appendChild( id(new AphrontFormTextControl()) ->setName(self::WEPAY_ACCOUNT_ID) ->setValue($values[self::WEPAY_ACCOUNT_ID]) ->setError(idx($issues, self::WEPAY_ACCOUNT_ID, true)) ->setLabel(pht('WePay Account ID'))); } public function getPaymentMethodDescription() { return pht('Credit Card or Bank Account'); } public function getPaymentMethodIcon() { - return celerity_get_resource_uri('/rsrc/image/phortune/wepay.png'); + return 'WePay'; } public function getPaymentMethodProviderDescription() { return 'WePay'; } protected function executeCharge( PhortunePaymentMethod $payment_method, PhortuneCharge $charge) { throw new Exception('!'); } private function getWePayClientID() { return $this ->getProviderConfig() ->getMetadataValue(self::WEPAY_CLIENT_ID); } private function getWePayClientSecret() { return $this ->getProviderConfig() ->getMetadataValue(self::WEPAY_CLIENT_SECRET); } private function getWePayAccessToken() { return $this ->getProviderConfig() ->getMetadataValue(self::WEPAY_ACCESS_TOKEN); } private function getWePayAccountID() { return $this ->getProviderConfig() ->getMetadataValue(self::WEPAY_ACCOUNT_ID); } /* -( One-Time Payments )-------------------------------------------------- */ public function canProcessOneTimePayments() { return true; } /* -( Controllers )-------------------------------------------------------- */ public function canRespondToControllerAction($action) { switch ($action) { case 'checkout': case 'charge': case 'cancel': return true; } return parent::canRespondToControllerAction(); } /** * @phutil-external-symbol class WePay */ public function processControllerRequest( PhortuneProviderActionController $controller, AphrontRequest $request) { $viewer = $request->getUser(); $cart = $controller->loadCart($request->getInt('cartID')); if (!$cart) { return new Aphront404Response(); } $root = dirname(phutil_get_library_root('phabricator')); require_once $root.'/externals/wepay/wepay.php'; WePay::useStaging( $this->getWePayClientID(), $this->getWePayClientSecret()); $wepay = new WePay($this->getWePayAccessToken()); $charge = $controller->loadActiveCharge($cart); switch ($controller->getAction()) { case 'checkout': if ($charge) { throw new Exception(pht('Cart is already charging!')); } break; case 'charge': case 'cancel': if (!$charge) { throw new Exception(pht('Cart is not charging yet!')); } break; } switch ($controller->getAction()) { case 'checkout': $return_uri = $this->getControllerURI( 'charge', array( 'cartID' => $cart->getID(), )); $cancel_uri = $this->getControllerURI( 'cancel', array( 'cartID' => $cart->getID(), )); $price = $cart->getTotalPriceAsCurrency(); $params = array( 'account_id' => $this->getWePayAccountID(), 'short_description' => 'Services', // TODO 'type' => 'SERVICE', 'amount' => $price->formatBareValue(), 'long_description' => 'Services', // TODO 'reference_id' => $cart->getPHID(), 'app_fee' => 0, 'fee_payer' => 'Payee', 'redirect_uri' => $return_uri, 'fallback_uri' => $cancel_uri, // NOTE: If we don't `auto_capture`, we might get a result back in // either an "authorized" or a "reserved" state. We can't capture // an "authorized" result, so just autocapture. 'auto_capture' => true, 'require_shipping' => 0, 'shipping_fee' => 0, 'charge_tax' => 0, 'mode' => 'regular', 'funding_sources' => 'bank,cc', ); $charge = $cart->willApplyCharge($viewer, $this); $result = $wepay->request('checkout/create', $params); $cart->setMetadataValue('provider.checkoutURI', $result->checkout_uri); $cart->save(); $charge->setMetadataValue('wepay.checkoutID', $result->checkout_id); $charge->save(); $uri = new PhutilURI($result->checkout_uri); return id(new AphrontRedirectResponse()) ->setIsExternal(true) ->setURI($uri); case 'charge': $checkout_id = $request->getInt('checkout_id'); $params = array( 'checkout_id' => $checkout_id, ); $checkout = $wepay->request('checkout', $params); if ($checkout->reference_id != $cart->getPHID()) { throw new Exception( pht('Checkout reference ID does not match cart PHID!')); } switch ($checkout->state) { case 'authorized': case 'reserved': case 'captured': break; default: throw new Exception( pht( 'Checkout is in bad state "%s"!', $result->state)); } $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); $cart->didApplyCharge($charge); unset($unguarded); return id(new AphrontRedirectResponse()) ->setURI($cart->getDoneURI()); case 'cancel': // TODO: I don't know how it's possible to cancel out of a WePay // charge workflow. throw new Exception( pht('How did you get here? WePay has no cancel flow in its UI...?')); break; } throw new Exception( pht('Unsupported action "%s".', $controller->getAction())); } } diff --git a/webroot/rsrc/image/phortune/balanced.png b/webroot/rsrc/image/phortune/balanced.png deleted file mode 100644 index ef94cd3ca8..0000000000 Binary files a/webroot/rsrc/image/phortune/balanced.png and /dev/null differ diff --git a/webroot/rsrc/image/phortune/paypal.png b/webroot/rsrc/image/phortune/paypal.png deleted file mode 100644 index ef94cd3ca8..0000000000 Binary files a/webroot/rsrc/image/phortune/paypal.png and /dev/null differ diff --git a/webroot/rsrc/image/phortune/stripe.png b/webroot/rsrc/image/phortune/stripe.png deleted file mode 100644 index ef94cd3ca8..0000000000 Binary files a/webroot/rsrc/image/phortune/stripe.png and /dev/null differ diff --git a/webroot/rsrc/image/phortune/test.png b/webroot/rsrc/image/phortune/test.png deleted file mode 100644 index ef94cd3ca8..0000000000 Binary files a/webroot/rsrc/image/phortune/test.png and /dev/null differ diff --git a/webroot/rsrc/image/phortune/wepay.png b/webroot/rsrc/image/phortune/wepay.png deleted file mode 100644 index ef94cd3ca8..0000000000 Binary files a/webroot/rsrc/image/phortune/wepay.png and /dev/null differ