138 Commits

Author SHA1 Message Date
9093816113 Phabricator: restore reviewers and CCs field in commit messages, they are needed
for arc diff.
2014-01-24 17:58:08 +01:00
3471c1b87e Fix T37772: Tab indentation missing in Diffusion
This seems to be a specific of how browsers are dealing with
spaces/tabs. Multiple spaces works just fine, but multiple
tabs were treating as a single space which breaks indentation.

Now made it so tabs are replaced with 4 spaces. Not ideal but
still better than fully unreadable code.
2014-01-17 17:05:14 +06:00
30f0de9427 Fix T38210: phabricator file downloads not working for non-logged in users. 2014-01-15 15:53:50 +01:00
5278e94566 Further arc commit messages tweak to follow guidelines, leave out CCs,
Reviewers (not Reviewed By), Maniphest Tasks and Summary label.
2014-01-04 05:37:58 +01:00
08b1e9d764 Fix buildbot link not working after move to https, redirect instead now. 2014-01-04 05:13:51 +01:00
30427e0626 Leave reviewers and CCs out of commit message. 2014-01-04 05:13:28 +01:00
752a5b9bc3 Further tweak to bug report page descriptions. 2013-12-29 18:25:34 +01:00
a5390d5461 Improve bug report page descriptions. 2013-12-29 18:23:02 +01:00
a02fa19d5a Fixed typo in previous commit, no functional changes. 2013-12-25 17:44:36 +06:00
f88bdcd4ff Truncate file to 10 sec on display with diffusion
Displaying huge files like .po used loads of CPU power
and might have easily run out of time or memory.

Now file will be truncated after 10 sec of view generation.
2013-12-25 17:34:24 +06:00
b98f8a570c Added a way to generate login=>(email, real name) map
Also fixed a typo in custom field in user change password function.
2013-11-29 22:58:33 +06:00
644070340d Add list of all archived repositories 2013-11-28 15:39:25 +06:00
93fecfe7e9 Added list of archived repositories which RO access to them
Currently only bf-extensions, rest of the repos will arrive soon.
2013-11-28 15:19:32 +06:00
53bb3ac361 Make sure all the users have RO access to the repo 2013-11-28 15:09:37 +06:00
137df8c6d3 Make sure there's no trailing slash in svn's subpath
This gives issues with svn access file, leading to
access denied by the server.
2013-11-28 14:57:24 +06:00
9863285bf0 Fix missing project action list on mobile. 2013-11-27 20:47:30 +01:00
a392b4a14f Fix typo in svn authentification generator script 2013-11-28 00:21:59 +06:00
d03569e908 Correction for the previous commit 2013-11-27 22:20:55 +06:00
b1097e9c61 Fix for crash when pushable project have unknown users
Apparently we've got Unknown Object (Phabricator User) in
translation project now.

Not sure how it became invalid, but automated scripts
better be robust for this.
2013-11-27 22:14:07 +06:00
5bda6bf48b Attempt to make SVN happy with untrusted server certificate
Need this to fetch changes from our svn.b.o which have untrusted
issuer of the certificate.
2013-11-27 19:36:13 +06:00
a03cdacd9e Tweaks to svn access rights importer
- Make it work with repositories outside of svn.b.o.
  Useful for local debugging.

- Make it aware of svn subpath repository setting.
  So now it's possible to restrict commit rights
  to a subpath in svn repository.

- Make SVN repositories always available for
  read-only access;
2013-11-27 18:37:37 +06:00
c3fe33f5a0 Remove debug-only code from svn auth script 2013-11-27 02:25:47 +06:00
5d823ff1a7 Fix T37436: Add a method to show the bug count
Now query header will show text like:

  N total tasks for query "Foo"
2013-11-22 00:24:14 +06:00
63df9dd1f4 Undo test commit. 2013-11-21 19:10:36 +01:00
0c80c9fead Add "Open Revisions" builtin query for differential, and have links for both
code reviews and patches on welcome page.
2013-11-21 19:05:38 +01:00
b8d42a7cc6 Test commit for disabling merge commits. 2013-11-21 19:05:38 +01:00
4e7dc30fea Fix typo in method name 2013-11-21 23:17:13 +06:00
99c68b1091 Fix T37529: show up to 4 project tags for maniphest tasks instead of 2. 2013-11-19 16:14:03 +01:00
d48979091e Maniphest: order open tasks by date created and don't group by priority, to
make it more like the old bug tracker. Also makes count bugs fairly easy by
just looking at the number on the second or third page.
2013-11-17 10:21:38 +01:00
785caa5b6c Test commit for mail hooks, would prefer to do not it on live install but
that seems to be the only place it fails.
2013-11-16 04:22:04 +01:00
261d7331f4 Remove some unnecessary code from last commit. 2013-11-16 02:49:55 +01:00
28bd8409df Remove unnecessary duplicate buildbot crumb. 2013-11-16 02:44:17 +01:00
47008a157c Browse Patches pointed to a wrong project 2013-11-16 00:00:02 +06:00
fc1e8681ef Add application name in crumbs display. 2013-11-15 17:30:28 +01:00
17085b4379 Try to clarify that the select projects listed on the home page are not all
projects.
2013-11-15 17:30:28 +01:00
dd6eefcd9c Make "Active" projects the default instead of "Joined". 2013-11-15 17:30:28 +01:00
ae2b0e63d6 Further tweaks to gi ermissions script
- Seems @all didn't work, now use explicit list of repos
- Handle cases when phab user uses the same key as used for
  "system" repositories.
2013-11-15 20:19:20 +06:00
bcbae097de Another attempt to make phab sync working 2013-11-15 18:55:16 +06:00
b59dca4eeb Made it so public keys are stored in files with .pub extension
It is an attempt to solve issues with missing permissions on
pahabricator users.
2013-11-15 18:49:56 +06:00
8098798fa9 Fix T37435: clicking maniphest next button with no /query in the URL would lose
the default query and show the search form on the second page.

This was a bug in Phabricator itself, just not as likely to happen because we
have the default query as Open Tasks while they have Assigned, which is unlikely
to give more than 100 results and so there would be no next button.
2013-11-15 02:46:28 +01:00
d8d0e2c99f Fix T37447: Add a link to project list when creating tasks. 2013-11-15 00:32:18 +01:00
214233f82f Show author name in task list views.
Fixes T37451.
2013-11-15 00:04:00 +01:00
eebe82c990 Assume 4 space for tabs by default in differential. 2013-11-14 23:27:09 +01:00
5b73071d64 Removed debug-only left-over 2013-11-15 02:29:03 +06:00
cd04de7f72 Order diffusion repositories by name (so Blender is at the top). 2013-11-14 19:44:47 +01:00
cf4b1317e1 Fix T37441: Could not assign tasks to self (via the "Assigned To" box)
Issue was caused by our database having login "watch" which is
apparently an object's method in firefox.

Added workaround for this into the code.
2013-11-14 21:24:26 +06:00
10e353b090 Allow anyone to login to developer.blender.org again. 2013-11-14 12:15:23 +01:00
81bd756ec6 Also correct sheband for svn auth script 2013-11-14 13:34:55 +06:00
9527d8aafd tweak to shebang so cron is happy with it 2013-11-14 13:31:50 +06:00
e35bc58e0e Fixes for gitosis admin script
Basically, pull wasn't happening correct and it was
updating files from CWD< not from workdir.
2013-11-14 12:44:59 +06:00
bd0f9fd913 Add sculpting and websites projects to welcome page. 2013-11-13 19:53:22 +01:00
44957f7806 Get repsitory name from svn URI
Would only work for repos inside svn.b.o.
Some further checks might be needed.
2013-11-14 00:21:04 +06:00
7ff26eb95d Show message that developer.blender.org is not open yet for logins. 2013-11-13 19:10:10 +01:00
1f2471fa67 Use git repository name from uri. not from repo name 2013-11-14 00:09:40 +06:00
7a5588a408 Appeared a typo in previous commmit
Didn't notice on system where had htaccess custom field.
2013-11-13 14:37:47 +06:00
ac03ae737f Fix for recent htaccess hack broke user registration
Issue was caused by custom fields loading will set
user's profile. In case user is new, this profile
will have title and blurb set to NULL, which is
unsupported.

Solved by setting title and blurb to an empty string
if setPassword detects that this is a new user.

tested with accountadin script, need to be tested
with online registration as well.
2013-11-13 14:35:29 +06:00
f9db95f555 Add a missing translations project member. 2013-11-13 09:06:25 +01:00
2bc045a997 * Add another duplicate email
* Remove addon testing tmp code
* Make files public viewable
2013-11-13 09:03:09 +01:00
531b0fa529 Added script to generate svn auth files
This script only generates content of either svnroot-access
or svnroot-authfile (depending on command line arguments).

It's not directly usable yet and would need some magic to
make it able to pass the data to svn.b.o. But this is up to
machines setup, not to the script.
2013-11-12 23:47:17 +06:00
6b1ed4fb9b Seems we need to load custom field manully 2013-11-12 23:46:10 +06:00
645a6a9f54 Made rebuild_gitadmin only take git repositories into account 2013-11-12 22:43:01 +06:00
88cd291b0f Only allow admins to login and disable new account registration.
This is a temporary change to avoid accidental interference during import tomorrow.
2013-11-12 14:55:36 +01:00
76e4aaf429 Import unix passwords for svn access, and fix passwrod/password typo. 2013-11-12 14:29:03 +01:00
365f8ad981 Added instructions about htaccess_passowrd_hash hidden field 2013-11-12 18:10:06 +06:00
0af08f9469 Added hack to user class to set htaccess hash when setting password
Uses hash generation from gforge code. Wouldn't work correct for
imported users yet.

TODO: Imported users are to use hash from projects.b.o's database.
2013-11-12 18:02:37 +06:00
23ce658af7 Made it possible to mark custom fields as hidden
it is done using "hidden": true setting in custom
field specification.

Hidden fields will never be displayed and their
intention is to be used by autohmatization scripts.
2013-11-12 18:02:37 +06:00
0a54474f98 Better Submit Addon page. 2013-11-11 21:00:30 +01:00
93ca9ed3a8 Update docs for migration 2013-11-12 00:58:04 +06:00
d110fdccc2 Added options to disable lint and unit tests in diffusion 2013-11-12 00:53:39 +06:00
4197d983bf Fix 404 after clicking Find Owners in diffusion, since we have this application disabled. 2013-11-11 17:49:03 +01:00
d23883b799 Update design task and todo task instructions. 2013-11-09 15:46:03 +01:00
675d2ac374 Link to addon docs on submit patch page. 2013-11-09 15:25:03 +01:00
5c86735ab7 On projects pages, add Submit Patch, Add To Do, Add Design Task links. It's all
a bit hardcoded but makes the expected workflow easier to understand.
2013-11-09 15:12:40 +01:00
566274dcac Patch/diff html pages updates. 2013-11-08 22:01:38 +01:00
de2814545b Add Submit Patch, Create Design Task and Create To Do actions in Maniphest when
browsing the corresponding.
2013-11-08 21:01:07 +01:00
4ea26f22c4 Add html instructions for diff/patch, todo and design tasks. 2013-11-08 20:55:53 +01:00
458bc7f921 Update task import script to handle addons. 2013-11-07 18:30:39 +01:00
3839698e4a Remove unnecessary whitespace. 2013-11-07 16:54:39 +01:00
b403863e62 Correction to documentation, extra bachslashes 2013-11-07 20:41:56 +06:00
246b73f922 Fix type in previous commit, sorry 2013-11-07 20:39:56 +06:00
6da7f462a9 Fix for documentation 2013-11-07 20:36:43 +06:00
90874b8b00 Links which goes to different site better open pages in new tab 2013-11-07 20:33:52 +06:00
7e874a33fb Added instructions to patch submission form. 2013-11-07 20:33:51 +06:00
bbe357e5c0 Add [developer.blender.org] in mail subject instead of only [Maniphest], [Diffusion], etc.
For casual users it's important to be able to see where the message comes from.
2013-11-07 20:33:51 +06:00
428cdae3cf Further tweaks to gitosis conf rebuild script
- Pull need SSH key as well
- Repository names are ensured to be lower case
- Use verbose name for commti author
2013-11-07 20:33:51 +06:00
1c689a6b24 Correct shebang for gitx-ssh 2013-11-07 20:33:51 +06:00
55972ef2c1 Gitadmin configuration now could be commited and pushed from update script 2013-11-07 20:33:51 +06:00
966687d1ab Project pages now have a Report Bug link, and View Tasks now uses the project
filter rather than setting up a query.
2013-11-07 20:33:51 +06:00
6a86144adf Another update to migration steps. 2013-11-07 20:33:51 +06:00
57765e88c0 Update migration steps with mailhook.
This commit also tests the mailhook itself, if all goes well.
2013-11-07 20:33:51 +06:00
60cddad047 Whitespace cleanup 2013-11-07 20:33:51 +06:00
a77a33dc50 Rework gitosis config generator to use pushable settings from repository 2013-11-07 20:33:51 +06:00
405a07e638 Made 'pushable' of repository editable for non-hosted repos as well 2013-11-07 20:33:51 +06:00
75f9ce9d77 Missed this in previous 'revert changes' commit 2013-11-07 20:33:50 +06:00
259588e747 Added script which rebuilds gitadmin configuration
It generates new gitosis.conf and public key files.
Actual commit to repo would likely be done with a
wrapper script which will run in cron.
2013-11-07 20:33:50 +06:00
0e8b746fb0 Tweaks to page and mail titles
- Welcome page will now have title "Blender Foundation: Welcome",
  the same as in old projects.b.o.
- Mail will be sent from name of BF, not phabricator, seems to
  be quite logical to do.
2013-11-07 20:33:50 +06:00
753917c10c Revert changes in AphrontFormPolicyControl.php
After recent Brecht's commit they're not needed.

Euh, didn't notice policies could be edited in configuration :(
2013-11-07 20:33:50 +06:00
39f7364f25 Tweaks to maniphest
- Remove quick links to create tasks, now only
  "Report Bug" to a specified project is allowed.
- Added 'Addons' to list of 'always visible'
  in navigation bar projects.
2013-11-07 20:33:50 +06:00
8d7de5fcb6 Tasks: don't force particular policy in the code here, let it be defined by the
default policies configured for maniphest.
2013-11-07 20:33:50 +06:00
01398f762b Better looks and css for welcome.html. For reference, __celerity_resource_map__.php
was updated with this command.

./scripts/celerity_mapper.php --with-custom webroot
2013-11-07 20:33:50 +06:00
e12d2efe81 Add buildbot and developer wiki links in the menu. 2013-11-07 20:33:23 +06:00
5c7055d840 Reshuffle order of builtin queries 2013-11-07 20:33:23 +06:00
562c7aa126 Use blender.org's favicon 2013-11-07 20:33:22 +06:00
5994088e50 Tweaks to welcome and bug report pages, making them a bit prettier and hopefully
more clear.
2013-11-07 20:33:22 +06:00
9edbe22e19 Update welcome.html with correct project IDs. 2013-11-07 20:33:22 +06:00
7fabfff397 Update migration steps with info from test run of importing to developer.blender.org. 2013-11-07 20:33:22 +06:00
356a2fe0e8 Rework task submission form
- It is possible now to submit tasks to specified project.
- All the extra fields which are not interesting got us
  are now hidden.
- Default visibility is User (maybe we might want
  "Everyone" in fact)?
- Only Administrators are able to edit issues by
  default.
- Links to bug report are available from welcome page
  and from project's maniphest page.
- Added file with report bug guidelines. Currently
  just copy-pasted it from project.b.o, some style
  tweaks are welcome.
2013-11-07 20:33:22 +06:00
54193ebb15 Add link to all types of tasks within single project 2013-11-07 20:33:22 +06:00
2b4bb9cddd Highlight current task type in side menu
So now Bug,Patch,etc categories are always visible
and active one is being highlighted.
2013-11-07 20:33:22 +06:00
2758907517 Add some new duplicate email users for migration. 2013-11-07 20:33:22 +06:00
a7f641eee2 Allow public access to main page, with login button in the top right, and show
activity feed on the main page.
2013-11-07 20:33:22 +06:00
9799ceff80 More fixes for migration, in particular for setting policies on projects and tasks. 2013-11-07 20:33:22 +06:00
26e86c2171 Add missing files and minor migration tweaks. 2013-11-07 20:33:22 +06:00
f6c293fb4d Migration tweaks, and patch to temporarily disable sending mails and updating feeds
during import of data.
2013-11-07 20:33:22 +06:00
5abc5cf9f8 Lots of little fixes for import, status should be imported mostly OK now.
A dded a migration_steps.txt that outlines the steps to migrate data.

Still todo:
* Extensions/addons, skipped now, will do next week
* Hide some unnecessary transactions
* Figure out policies for tasks and projects
* Test run over all tasks to see that they work
2013-11-07 20:33:22 +06:00
41a820fdf0 Tweak appearance of projects page to show description more prominent, and
minor tweaks to closed status and welcome html margins.
2013-11-07 20:33:22 +06:00
2c8b09da21 Welcome page tweaks (links are mostly broken, but shows proposed structure). 2013-11-07 20:33:22 +06:00
a7ff526360 Migration: add system to warn users with merged accounts to use the other account,
and provide an option to change the username to their choice after login.
2013-11-07 20:33:21 +06:00
1a8148be97 Correct link to blender bug tracker 2013-11-07 20:33:21 +06:00
199dd85140 Show project and task type in query result header
This is intended to make it more clear which task
types are currently being displayed.

Needed to revoke "final" modifier from class
PhabricatorApplicationSearchController and make
it use separate method to get description for
this.
2013-11-07 20:33:21 +06:00
f6c44fdb03 Hide inactive projects and add button to show them all
For now uses stupid hardcoded list of "interesting"
projects. Might be changed to something smarter later.
2013-11-07 20:33:21 +06:00
1aade72cb1 Add extra filter level to filter tasks by type 2013-11-07 20:33:21 +06:00
121c9d308b Fix migration script error due to changed phab API. 2013-11-07 20:33:21 +06:00
d60a47ab5b Add migration/dump to gitignore so it's not commited by accident 2013-11-07 20:33:21 +06:00
8af9b8796f Fkx for syntax error, unexpected T_OBJECT_OPERATOR 2013-11-07 20:33:21 +06:00
7dd7673fb3 Added per-project categories to the side bar of maniphest
Currently adds all the projects there, and at this moment
current project is not being highlighted, it's only get
displayed on the top anchor bar.

TODO:
- We would probably wouldn't want all the projects there,
  need to add some filter there.
- Make it ore clear which project is currently active.
2013-11-07 20:33:21 +06:00
aeaf0902e1 Migration scripts in somewhat better state now, main missing part is that statuses
are not imported correctly, though there's a bunch of details to figure out.
2013-11-07 20:33:21 +06:00
498842475c Initial scripts for migration of data from gforge to phabricator.
Import of tasks is still a hacked together mess from before I figured out the
structure that we want.
2013-11-07 20:33:21 +06:00
755177508e Modify LiskDAO to support overriding ID and dates, used for import of data. 2013-11-07 20:33:21 +06:00
5b49fd93e2 Don't show edit button for tasks if editing is not allowed 2013-11-07 20:33:21 +06:00
968ea35b03 Use blender logo in header 2013-11-07 20:33:21 +06:00
97ed90b82a Fix for tiy typo in previous commit 2013-11-07 20:33:21 +06:00
ba34aa535a Move custom login form text to an html file
Also committing all the template files needed for
making phabricator closer to how our gforge looks.
2013-11-07 20:33:21 +06:00
e56041da3b Hide "Create Project" if it's not allowed for current user
Having a button which says "You are not allowed to press it"
is pretty much strange behavior. Better not to have such button
at first place.

For now it only affects Projects application. There're also
other places where we might want this change.
2013-11-07 20:33:20 +06:00
b19fbc39af Modification so we can import gforge passwords, by adding an extra md5 to the
password hash. By skipping this step on import, we keep passwords working.
2013-11-07 20:33:20 +06:00
1036d419fa Add "Archived" status for closing tasks. 2013-11-07 20:33:20 +06:00
0011789443 Added welcome.file setting
This setting operates quite the same as welcome.html
except for the difference that it points to a file
from which to get the HTML code.

It's more convenient to use file with HTML code if
one need to display rather long welcome screen.
2013-11-07 20:33:20 +06:00
ff7d71fb87 Make install_ubuntu.sh work for Debian as well 2013-11-07 20:33:20 +06:00
11475 changed files with 205712 additions and 795710 deletions

View File

@@ -1,5 +1,11 @@
{
"phabricator.uri": "https://secure.phabricator.com/",
"load": ["src/"],
"history.immutable": false
"project_id" : "phabricator",
"conduit_uri" : "https://secure.phabricator.com/api/",
"lint.engine" : "PhabricatorLintEngine",
"unit.engine" : "PhutilUnitTestEngine",
"phutil_libraries" : {
"phabricator" : "src/"
},
"lint.xhpast.naminghook" : "PhabricatorSymbolNameLinter",
"lint.jshint.config" : "support/jshint/jshintconfig"
}

View File

@@ -1,85 +0,0 @@
{
"exclude": [
"(^externals/)",
"(^webroot/rsrc/externals/(?!javelin/))",
"(/__tests__/data/)"
],
"linters": {
"chmod": {
"type": "chmod"
},
"filename": {
"type": "filename"
},
"generated": {
"type": "generated"
},
"javelin": {
"type": "javelin",
"include": "(\\.js$)",
"exclude": [
"(^support/aphlict/)"
]
},
"jshint-browser": {
"type": "jshint",
"include": "(\\.js$)",
"exclude": [
"(^support/aphlict/server/.*\\.js$)",
"(^webroot/rsrc/externals/javelin/core/init_node\\.js$)"
],
"jshint.jshintrc": "support/lint/browser.jshintrc"
},
"jshint-node": {
"type": "jshint",
"include": [
"(^support/aphlict/server/.*\\.js$)",
"(^webroot/rsrc/externals/javelin/core/init_node\\.js$)"
],
"jshint.jshintrc": "support/lint/node.jshintrc"
},
"json": {
"type": "json",
"include": [
"(^src/docs/book/.*\\.book$)",
"(^support/lint/jshintrc$)",
"(^\\.arcconfig$)",
"(^\\.arclint$)",
"(\\.json$)"
]
},
"merge-conflict": {
"type": "merge-conflict"
},
"nolint": {
"type": "nolint"
},
"phutil-library": {
"type": "phutil-library",
"include": "(\\.php$)"
},
"spelling": {
"type": "spelling"
},
"text": {
"type": "text",
"exclude": [
"(^src/(.*/)?__tests__/[^/]+/.*\\.(txt|json|expect))"
]
},
"text-without-length": {
"type": "text",
"include": [
"(^src/(.*/)?__tests__/[^/]+/.*\\.(txt|json|expect))"
],
"severity": {
"3": "disabled"
}
},
"xhpast": {
"type": "xhpast",
"include": "(\\.php$)",
"standard": "phutil.xhpast"
}
}
}

View File

@@ -1,8 +0,0 @@
{
"engines": {
"phutil": {
"type": "phutil",
"include": "(\\.php$)"
}
}
}

33
.divinerconfig Normal file
View File

@@ -0,0 +1,33 @@
{
"name" : "Phabricator",
"src_link" : "https://secure.phabricator.com/diffusion/P/browse/master/%f$%l",
"groups" : {
"intro" : "Introduction",
"config" : "Configuration",
"userguide" : "Application User Guides",
"contrib" : "Contributing",
"developer" : "Phabricator Developer Guides",
"flavortext" : "Flavor Text",
"differential" : "Differential (Code Review)",
"diffusion" : "Diffusion (Repository Browser)",
"maniphest" : "Maniphest (Task Tracking)",
"slowvote" : "Slowvote (Polls)",
"herald" : "Herald (Notifications)",
"conduit" : "Conduit (Phabricator HTTP API)",
"celerity" : "Celerity (CSS/JS Management)",
"phriction" : "Phriction (Wiki)",
"aphront" : "Aphront (Web Stack)",
"console" : "DarkConsole (Debugging Console)",
"storage" : "Storage",
"filestorage" : "File Storage",
"search" : "Search",
"daemon" : "Daemons, Tasks and Workers",
"irc" : "IRC",
"markup" : "Remarkup Extensions",
"metamta" : "MetaMTA (Mail)"
},
"engines" : [
["DivinerArticleEngine", {}],
["DivinerXHPEngine", {}]
]
}

View File

@@ -3,31 +3,12 @@
[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
max_line_length = 80
[.arclint]
max_line_length =
[resources/sql/**.sql]
max_line_length =
[scripts/install/install_*.sh]
max_line_length =
[src/applications/differential/parser/__tests__/data/*.diff]
trim_trailing_whitespace = false
[src/applications/differential/parser/__tests__/messages/long-title.txt]
max_line_length =
[src/applications/diffusion/ssh/__tests__/hgwiredata/*.txt]
max_line_length =
[externals/**]
; Use editor default (possible autodetection).
indent_style =
indent_size =
trim_trailing_whitespace = false

36
.gitignore vendored
View File

@@ -1,10 +1,23 @@
# NOTE: Thinking about adding files created by your operating system, IDE,
# or text editor here? Don't! Add them to your per-user .gitignore instead.
.DS_Store
._*
/webroot/rsrc/custom
.#*
*#
*~
*.swp
# NetBeans project files
/nbproject/
# PhpStorm project files
.idea/
# Arcanist scratch directory
/.arc
# Diviner
/docs/
/.divinercache/
/src/.cache/
/.divinercache
# libphutil
/src/.phutil_module_cache
@@ -14,10 +27,6 @@
/conf/local/local.json
/conf/local/ENVIRONMENT
/conf/local/VERSION
/conf/keys/device.pub
/conf/keys/device.key
/conf/keys/device.id
/conf/aphlict/aphlict.custom.json
# Impact Font
/resources/font/impact.ttf
@@ -25,18 +34,11 @@
# User-accessible hook for adhoc debugging scripts
/support/debug.php
# User-accessible hook for adhoc startup code
/support/preamble.php
# Users can link binaries here
/support/bin/*
# User extensions
/src/extensions/*
# NPM local packages
/support/aphlict/server/node_modules/
# Places for users to add custom resources.
/resources/cows/custom/*
/resources/figlet/custom/*
# blender migration files
migration/dump/

View File

@@ -1,3 +1,4 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/

22
NOTICE
View File

@@ -1,21 +1,7 @@
Phabricator
Copyright 2014 Phacility, Inc.
Copyright 2013 Facebook, Inc.
This software is primarily developed and maintained by Phacility, Inc.
This product includes software developed at
Facebook, Inc. (http://www.facebook.com/facebook).
http://www.phacility.com/
Portions of this software were developed by various contributors, who retain
copyright on their work. These works are licensed to Phacility, Inc.
Phabricator is available under the Apache 2.0 license. See LICENSE for more
information.
Phabricator and Phacility are trademarks of Phacility, Inc. For additional
information about trademarks that pertain to this software, see:
http://www.phacility.com/trademarks/
This software uses other open source libraries, which are located in
"externals/" and "webroot/rsrc/externals/". These libraries have their own
licenses and copyright holders.
Libraries in externals/ have their own licenses and copyright holders.

13
README Normal file
View File

@@ -0,0 +1,13 @@
Phabricator is an open source collection of web applications which make it
easier to write, review, and share source code. Phabricator was developed at
Facebook.
It's pretty high-quality and usable, but under active development so things
may change quickly.
You can learn more about the project and find links to documentation and
resources at: http://phabricator.org/
LICENSE
Phabricator is released under the Apache 2.0 license except as otherwise noted.

View File

@@ -1,31 +0,0 @@
**Phabricator** is a collection of web applications which help software companies build better software.
Phabricator includes applications for:
- reviewing and auditing source code;
- hosting and browsing repositories;
- tracking bugs;
- managing projects;
- conversing with team members;
- assembling a party to venture forth;
- writing stuff down and reading it later;
- hiding stuff from coworkers; and
- also some other things.
You can learn more about the project (and find links to documentation and resources) at [Phabricator.org](http://phabricator.org)
Phabricator is developed and maintained by [Phacility](http://phacility.com).
----------
**SUPPORT RESOURCES**
For resources on filing bugs, requesting features, reporting security issues, and getting other kinds of support, see [Support Resources](https://secure.phabricator.com/book/phabricator/article/support/).
**NO PULL REQUESTS!**
We do not accept pull requests through GitHub. If you would like to contribute code, please read our [Contributor's Guide](https://secure.phabricator.com/book/phabcontrib/article/contributing_code/).
**LICENSE**
Phabricator is released under the Apache 2.0 license except as otherwise noted.

1
bin/accountadmin Symbolic link
View File

@@ -0,0 +1 @@
../scripts/user/account_admin.php

View File

@@ -1 +0,0 @@
../scripts/almanac/manage_almanac.php

View File

@@ -1 +0,0 @@
../scripts/setup/manage_bulk.php

View File

@@ -1 +0,0 @@
../scripts/setup/manage_calendar.php

View File

@@ -1 +0,0 @@
../scripts/setup/manage_celerity.php

View File

@@ -1 +0,0 @@
../scripts/repository/commit_hook.php

View File

@@ -1 +0,0 @@
../scripts/setup/manage_conduit.php

View File

@@ -1 +0,0 @@
../scripts/setup/manage_differential.php

View File

@@ -1 +0,0 @@
../scripts/setup/manage_garbage.php

View File

@@ -1 +0,0 @@
../scripts/setup/manage_harbormaster.php

View File

@@ -1 +0,0 @@
../scripts/setup/manage_herald.php

View File

@@ -1 +0,0 @@
../scripts/setup/manage_i18n.php

View File

@@ -1 +0,0 @@
../scripts/setup/manage_lock.php

View File

@@ -1 +0,0 @@
../scripts/setup/manage_nuance.php

View File

@@ -1 +0,0 @@
../scripts/setup/manage_phortune.php

View File

@@ -1 +0,0 @@
../scripts/setup/manage_remove.php

1
bin/ssh-auth-key Symbolic link
View File

@@ -0,0 +1 @@
../scripts/ssh/ssh-auth-key.php

View File

@@ -1 +0,0 @@
../scripts/ssh/ssh-connect.php

View File

@@ -1 +0,0 @@
../scripts/setup/manage_trigger.php

View File

@@ -1 +0,0 @@
../scripts/setup/manage_user.php

View File

@@ -1 +0,0 @@
../scripts/setup/manage_webhook.php

View File

@@ -1 +0,0 @@
../scripts/setup/manage_worker.php

View File

@@ -1,49 +1,13 @@
<?php
function phabricator_read_config_file($original_config) {
$root = dirname(dirname(__FILE__));
// Accept either "myconfig" (preferred) or "myconfig.conf.php".
$config = preg_replace('/\.conf\.php$/', '', $original_config);
$full_config_path = $root.'/conf/'.$config.'.conf.php';
if (!Filesystem::pathExists($full_config_path)) {
// These are very old configuration files which we used to ship with
// by default. File based configuration was de-emphasized once web-based
// configuration was built. The actual files were removed to reduce
// user confusion over how to configure Phabricator.
switch ($config) {
case 'default':
case 'production':
return array();
case 'development':
return array(
'phabricator.developer-mode' => true,
'darkconsole.enabled' => true,
);
}
$files = id(new FileFinder($root.'/conf/'))
->withType('f')
->withSuffix('conf.php')
->withFollowSymlinks(true)
->find();
foreach ($files as $key => $file) {
$file = trim($file, './');
$files[$key] = preg_replace('/\.conf\.php$/', '', $file);
}
$files = ' '.implode("\n ", $files);
throw new Exception(
pht(
"CONFIGURATION ERROR\n".
"Config file '%s' does not exist. Valid config files are:\n\n%s",
$original_config,
$files));
}
// Make sure config file errors are reported.
$old_error_level = error_reporting(E_ALL | E_STRICT);
$old_display_errors = ini_get('display_errors');
@@ -57,11 +21,25 @@ function phabricator_read_config_file($original_config) {
ini_set('display_errors', $old_display_errors);
if ($conf === false) {
throw new Exception(
pht(
"Failed to read config file '%s': %s",
$config,
$errors));
if (!Filesystem::pathExists($full_config_path)) {
$files = id(new FileFinder($root.'/conf/'))
->withType('f')
->withSuffix('conf.php')
->withFollowSymlinks(true)
->find();
foreach ($files as $key => $file) {
$file = trim($file, './');
$files[$key] = preg_replace('/\.conf\.php$/', '', $file);
}
$files = " ".implode("\n ", $files);
throw new Exception(
"CONFIGURATION ERROR\n".
"Config file '{$original_config}' does not exist. Valid config files ".
"are:\n\n".$files);
}
throw new Exception("Failed to read config file '{$config}': {$errors}");
}
return $conf;

View File

@@ -1,16 +0,0 @@
To customize this configuration, you have two options: create a custom
configuration file in this directory, or specify a path to a configuration file
explicitly when starting Aphlict.
To create a custom configuration file, copy `aphlict.default.json` in this
directory and rename it `aphlict.custom.json`. If this file exists, it will
be read by default.
To specify a path when starting Aphlict, use the `--config` flag:
phabricator/ $ ./bin/aphlict start --config path/to/config.json
Specifying a configuration file explicitly overrides default configuration.
For more information about configuring notifications, see the article
"Notifications User Guide: Setup and Configuration" in the documentation.

View File

@@ -1,26 +0,0 @@
{
"servers": [
{
"type": "client",
"port": 22280,
"listen": "0.0.0.0",
"ssl.key": null,
"ssl.cert": null,
"ssl.chain": null
},
{
"type": "admin",
"port": 22281,
"listen": "127.0.0.1",
"ssl.key": null,
"ssl.cert": null,
"ssl.chain": null
}
],
"logs": [
{
"path": "/var/log/aphlict.log"
}
],
"pidfile": "/var/tmp/aphlict/pid/aphlict.pid"
}

1164
conf/default.conf.php Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,8 @@
<?php
return array(
'phabricator.developer-mode' => true,
'darkconsole.enabled' => true,
) + phabricator_read_config_file('default');

View File

6
conf/production.conf.php Normal file
View File

@@ -0,0 +1,6 @@
<?php
return array(
) + phabricator_read_config_file('default');

View File

@@ -13,11 +13,7 @@ function jsShrink($input) {
(?:
(^|[-+\([{}=,:;!%^&*|?~]|/(?![/*])|return|throw) # context before regexp
(?:\s|//[^\n]*+\n|/\*(?:[^*]|\*(?!/))*+\*/)* # optional space
(/(?![/*])(?:
\\\\[^\n]
|[^[\n/\\\\]++
|\[(?:\\\\[^\n]|[^]])++
)+/) # regexp
(/(?![/*])(?:\\\\[^\n]|[^[\n/\\\\]|\[(?:\\\\[^\n]|[^]])++)+/) # regexp
|(^
|\'(?:\\\\.|[^\n\'\\\\])*\'
|"(?:\\\\.|[^\n"\\\\])*"

742
externals/amazon-ses/ses.php vendored Normal file
View File

@@ -0,0 +1,742 @@
<?php
/**
*
* Copyright (c) 2011, Dan Myers.
* Parts copyright (c) 2008, Donovan Schonknecht.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
* This is a modified BSD license (the third clause has been removed).
* The BSD license may be found here:
* http://www.opensource.org/licenses/bsd-license.php
*
* Amazon Simple Email Service is a trademark of Amazon.com, Inc. or its affiliates.
*
* SimpleEmailService is based on Donovan Schonknecht's Amazon S3 PHP class, found here:
* http://undesigned.org.za/2007/10/22/amazon-s3-php-class
*
*/
/**
* Amazon SimpleEmailService PHP class
*
* @link http://sourceforge.net/projects/php-aws-ses/
* version 0.8.1
*
*/
class SimpleEmailService
{
protected $__accessKey; // AWS Access key
protected $__secretKey; // AWS Secret key
protected $__host;
public function getAccessKey() { return $this->__accessKey; }
public function getSecretKey() { return $this->__secretKey; }
public function getHost() { return $this->__host; }
protected $__verifyHost = 1;
protected $__verifyPeer = 1;
// verifyHost and verifyPeer determine whether curl verifies ssl certificates.
// It may be necessary to disable these checks on certain systems.
// These only have an effect if SSL is enabled.
public function verifyHost() { return $this->__verifyHost; }
public function enableVerifyHost($enable = true) { $this->__verifyHost = $enable; }
public function verifyPeer() { return $this->__verifyPeer; }
public function enableVerifyPeer($enable = true) { $this->__verifyPeer = $enable; }
// If you use exceptions, errors will be communicated by throwing a
// SimpleEmailServiceException. By default, they will be trigger_error()'d.
protected $__useExceptions = 0;
public function useExceptions() { return $this->__useExceptions; }
public function enableUseExceptions($enable = true) { $this->__useExceptions = $enable; }
/**
* Constructor
*
* @param string $accessKey Access key
* @param string $secretKey Secret key
* @return void
*/
public function __construct($accessKey = null, $secretKey = null, $host = 'email.us-east-1.amazonaws.com') {
if ($accessKey !== null && $secretKey !== null) {
$this->setAuth($accessKey, $secretKey);
}
$this->__host = $host;
}
/**
* Set AWS access key and secret key
*
* @param string $accessKey Access key
* @param string $secretKey Secret key
* @return void
*/
public function setAuth($accessKey, $secretKey) {
$this->__accessKey = $accessKey;
$this->__secretKey = $secretKey;
}
/**
* Lists the email addresses that have been verified and can be used as the 'From' address
*
* @return An array containing two items: a list of verified email addresses, and the request id.
*/
public function listVerifiedEmailAddresses() {
$rest = new SimpleEmailServiceRequest($this, 'GET');
$rest->setParameter('Action', 'ListVerifiedEmailAddresses');
$rest = $rest->getResponse();
if($rest->error === false && $rest->code !== 200) {
$rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
}
if($rest->error !== false) {
$this->__triggerError('listVerifiedEmailAddresses', $rest->error);
return false;
}
$response = array();
if(!isset($rest->body)) {
return $response;
}
$addresses = array();
foreach($rest->body->ListVerifiedEmailAddressesResult->VerifiedEmailAddresses->member as $address) {
$addresses[] = (string)$address;
}
$response['Addresses'] = $addresses;
$response['RequestId'] = (string)$rest->body->ResponseMetadata->RequestId;
return $response;
}
/**
* Requests verification of the provided email address, so it can be used
* as the 'From' address when sending emails through SimpleEmailService.
*
* After submitting this request, you should receive a verification email
* from Amazon at the specified address containing instructions to follow.
*
* @param string email The email address to get verified
* @return The request id for this request.
*/
public function verifyEmailAddress($email) {
$rest = new SimpleEmailServiceRequest($this, 'POST');
$rest->setParameter('Action', 'VerifyEmailAddress');
$rest->setParameter('EmailAddress', $email);
$rest = $rest->getResponse();
if($rest->error === false && $rest->code !== 200) {
$rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
}
if($rest->error !== false) {
$this->__triggerError('verifyEmailAddress', $rest->error);
return false;
}
$response['RequestId'] = (string)$rest->body->ResponseMetadata->RequestId;
return $response;
}
/**
* Removes the specified email address from the list of verified addresses.
*
* @param string email The email address to remove
* @return The request id for this request.
*/
public function deleteVerifiedEmailAddress($email) {
$rest = new SimpleEmailServiceRequest($this, 'DELETE');
$rest->setParameter('Action', 'DeleteVerifiedEmailAddress');
$rest->setParameter('EmailAddress', $email);
$rest = $rest->getResponse();
if($rest->error === false && $rest->code !== 200) {
$rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
}
if($rest->error !== false) {
$this->__triggerError('deleteVerifiedEmailAddress', $rest->error);
return false;
}
$response['RequestId'] = (string)$rest->body->ResponseMetadata->RequestId;
return $response;
}
/**
* Retrieves information on the current activity limits for this account.
* See http://docs.amazonwebservices.com/ses/latest/APIReference/API_GetSendQuota.html
*
* @return An array containing information on this account's activity limits.
*/
public function getSendQuota() {
$rest = new SimpleEmailServiceRequest($this, 'GET');
$rest->setParameter('Action', 'GetSendQuota');
$rest = $rest->getResponse();
if($rest->error === false && $rest->code !== 200) {
$rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
}
if($rest->error !== false) {
$this->__triggerError('getSendQuota', $rest->error);
return false;
}
$response = array();
if(!isset($rest->body)) {
return $response;
}
$response['Max24HourSend'] = (string)$rest->body->GetSendQuotaResult->Max24HourSend;
$response['MaxSendRate'] = (string)$rest->body->GetSendQuotaResult->MaxSendRate;
$response['SentLast24Hours'] = (string)$rest->body->GetSendQuotaResult->SentLast24Hours;
$response['RequestId'] = (string)$rest->body->ResponseMetadata->RequestId;
return $response;
}
/**
* Retrieves statistics for the last two weeks of activity on this account.
* See http://docs.amazonwebservices.com/ses/latest/APIReference/API_GetSendStatistics.html
*
* @return An array of activity statistics. Each array item covers a 15-minute period.
*/
public function getSendStatistics() {
$rest = new SimpleEmailServiceRequest($this, 'GET');
$rest->setParameter('Action', 'GetSendStatistics');
$rest = $rest->getResponse();
if($rest->error === false && $rest->code !== 200) {
$rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
}
if($rest->error !== false) {
$this->__triggerError('getSendStatistics', $rest->error);
return false;
}
$response = array();
if(!isset($rest->body)) {
return $response;
}
$datapoints = array();
foreach($rest->body->GetSendStatisticsResult->SendDataPoints->member as $datapoint) {
$p = array();
$p['Bounces'] = (string)$datapoint->Bounces;
$p['Complaints'] = (string)$datapoint->Complaints;
$p['DeliveryAttempts'] = (string)$datapoint->DeliveryAttempts;
$p['Rejects'] = (string)$datapoint->Rejects;
$p['Timestamp'] = (string)$datapoint->Timestamp;
$datapoints[] = $p;
}
$response['SendDataPoints'] = $datapoints;
$response['RequestId'] = (string)$rest->body->ResponseMetadata->RequestId;
return $response;
}
public function sendRawEmail($raw) {
$rest = new SimpleEmailServiceRequest($this, 'POST');
$rest->setParameter('Action', 'SendRawEmail');
$rest->setParameter('RawMessage.Data', base64_encode($raw));
$rest = $rest->getResponse();
if($rest->error === false && $rest->code !== 200) {
$rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
}
if($rest->error !== false) {
$this->__triggerError('sendRawEmail', $rest->error);
return false;
}
$response['MessageId'] = (string)$rest->body->SendEmailResult->MessageId;
$response['RequestId'] = (string)$rest->body->ResponseMetadata->RequestId;
return $response;
}
/**
* Given a SimpleEmailServiceMessage object, submits the message to the service for sending.
*
* @return An array containing the unique identifier for this message and a separate request id.
* Returns false if the provided message is missing any required fields.
*/
public function sendEmail($sesMessage) {
if(!$sesMessage->validate()) {
return false;
}
$rest = new SimpleEmailServiceRequest($this, 'POST');
$rest->setParameter('Action', 'SendEmail');
$i = 1;
foreach($sesMessage->to as $to) {
$rest->setParameter('Destination.ToAddresses.member.'.$i, $to);
$i++;
}
if(is_array($sesMessage->cc)) {
$i = 1;
foreach($sesMessage->cc as $cc) {
$rest->setParameter('Destination.CcAddresses.member.'.$i, $cc);
$i++;
}
}
if(is_array($sesMessage->bcc)) {
$i = 1;
foreach($sesMessage->bcc as $bcc) {
$rest->setParameter('Destination.BccAddresses.member.'.$i, $bcc);
$i++;
}
}
if(is_array($sesMessage->replyto)) {
$i = 1;
foreach($sesMessage->replyto as $replyto) {
$rest->setParameter('ReplyToAddresses.member.'.$i, $replyto);
$i++;
}
}
$rest->setParameter('Source', $sesMessage->from);
if($sesMessage->returnpath != null) {
$rest->setParameter('ReturnPath', $sesMessage->returnpath);
}
if($sesMessage->subject != null && strlen($sesMessage->subject) > 0) {
$rest->setParameter('Message.Subject.Data', $sesMessage->subject);
if($sesMessage->subjectCharset != null && strlen($sesMessage->subjectCharset) > 0) {
$rest->setParameter('Message.Subject.Charset', $sesMessage->subjectCharset);
}
}
if($sesMessage->messagetext != null && strlen($sesMessage->messagetext) > 0) {
$rest->setParameter('Message.Body.Text.Data', $sesMessage->messagetext);
if($sesMessage->messageTextCharset != null && strlen($sesMessage->messageTextCharset) > 0) {
$rest->setParameter('Message.Body.Text.Charset', $sesMessage->messageTextCharset);
}
}
if($sesMessage->messagehtml != null && strlen($sesMessage->messagehtml) > 0) {
$rest->setParameter('Message.Body.Html.Data', $sesMessage->messagehtml);
if($sesMessage->messageHtmlCharset != null && strlen($sesMessage->messageHtmlCharset) > 0) {
$rest->setParameter('Message.Body.Html.Charset', $sesMessage->messageHtmlCharset);
}
}
$rest = $rest->getResponse();
if($rest->error === false && $rest->code !== 200) {
$rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
}
if($rest->error !== false) {
$this->__triggerError('sendEmail', $rest->error);
return false;
}
$response['MessageId'] = (string)$rest->body->SendEmailResult->MessageId;
$response['RequestId'] = (string)$rest->body->ResponseMetadata->RequestId;
return $response;
}
/**
* Trigger an error message
*
* @internal Used by member functions to output errors
* @param array $error Array containing error information
* @return string
*/
public function __triggerError($functionname, $error)
{
if($error == false) {
$message = sprintf("SimpleEmailService::%s(): Encountered an error, but no description given", $functionname);
}
else if(isset($error['curl']) && $error['curl'])
{
$message = sprintf("SimpleEmailService::%s(): %s %s", $functionname, $error['code'], $error['message']);
}
else if(isset($error['Error']))
{
$e = $error['Error'];
$message = sprintf("SimpleEmailService::%s(): %s - %s: %s\nRequest Id: %s\n", $functionname, $e['Type'], $e['Code'], $e['Message'], $error['RequestId']);
}
if ($this->useExceptions()) {
throw new SimpleEmailServiceException($message);
} else {
trigger_error($message, E_USER_WARNING);
}
}
/**
* Callback handler for 503 retries.
*
* @internal Used by SimpleDBRequest to call the user-specified callback, if set
* @param $attempt The number of failed attempts so far
* @return The retry delay in microseconds, or 0 to stop retrying.
*/
public function __executeServiceTemporarilyUnavailableRetryDelay($attempt)
{
if(is_callable($this->__serviceUnavailableRetryDelayCallback)) {
$callback = $this->__serviceUnavailableRetryDelayCallback;
return $callback($attempt);
}
return 0;
}
}
final class SimpleEmailServiceRequest
{
private $ses, $verb, $parameters = array();
public $response;
/**
* Constructor
*
* @param string $ses The SimpleEmailService object making this request
* @param string $action action
* @param string $verb HTTP verb
* @return mixed
*/
function __construct($ses, $verb) {
$this->ses = $ses;
$this->verb = $verb;
$this->response = new STDClass;
$this->response->error = false;
}
/**
* Set request parameter
*
* @param string $key Key
* @param string $value Value
* @param boolean $replace Whether to replace the key if it already exists (default true)
* @return void
*/
public function setParameter($key, $value, $replace = true) {
if(!$replace && isset($this->parameters[$key]))
{
$temp = (array)($this->parameters[$key]);
$temp[] = $value;
$this->parameters[$key] = $temp;
}
else
{
$this->parameters[$key] = $value;
}
}
/**
* Get the response
*
* @return object | false
*/
public function getResponse() {
$params = array();
foreach ($this->parameters as $var => $value)
{
if(is_array($value))
{
foreach($value as $v)
{
$params[] = $var.'='.$this->__customUrlEncode($v);
}
}
else
{
$params[] = $var.'='.$this->__customUrlEncode($value);
}
}
sort($params, SORT_STRING);
// must be in format 'Sun, 06 Nov 1994 08:49:37 GMT'
$date = gmdate('D, d M Y H:i:s e');
$query = implode('&', $params);
$headers = array();
$headers[] = 'Date: '.$date;
$headers[] = 'Host: '.$this->ses->getHost();
$auth = 'AWS3-HTTPS AWSAccessKeyId='.$this->ses->getAccessKey();
$auth .= ',Algorithm=HmacSHA256,Signature='.$this->__getSignature($date);
$headers[] = 'X-Amzn-Authorization: '.$auth;
$url = 'https://'.$this->ses->getHost().'/';
// Basic setup
$curl = curl_init();
curl_setopt($curl, CURLOPT_USERAGENT, 'SimpleEmailService/php');
curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, ($this->ses->verifyHost() ? 2 : 0));
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, ($this->ses->verifyPeer() ? 1 : 0));
// Request types
switch ($this->verb) {
case 'GET':
$url .= '?'.$query;
break;
case 'POST':
curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $this->verb);
curl_setopt($curl, CURLOPT_POSTFIELDS, $query);
$headers[] = 'Content-Type: application/x-www-form-urlencoded';
break;
case 'DELETE':
$url .= '?'.$query;
curl_setopt($curl, CURLOPT_CUSTOMREQUEST, 'DELETE');
break;
default: break;
}
curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
curl_setopt($curl, CURLOPT_HEADER, false);
curl_setopt($curl, CURLOPT_URL, $url);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, false);
curl_setopt($curl, CURLOPT_WRITEFUNCTION, array(&$this, '__responseWriteCallback'));
curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true);
// Execute, grab errors
if (curl_exec($curl)) {
$this->response->code = curl_getinfo($curl, CURLINFO_HTTP_CODE);
} else {
$this->response->error = array(
'curl' => true,
'code' => curl_errno($curl),
'message' => curl_error($curl),
'resource' => $this->resource
);
}
@curl_close($curl);
// Parse body into XML
if ($this->response->error === false && isset($this->response->body)) {
$this->response->body = simplexml_load_string($this->response->body);
// Grab SES errors
if (!in_array($this->response->code, array(200, 201, 202, 204))
&& isset($this->response->body->Error)) {
$error = $this->response->body->Error;
$output = array();
$output['curl'] = false;
$output['Error'] = array();
$output['Error']['Type'] = (string)$error->Type;
$output['Error']['Code'] = (string)$error->Code;
$output['Error']['Message'] = (string)$error->Message;
$output['RequestId'] = (string)$this->response->body->RequestId;
$this->response->error = $output;
unset($this->response->body);
}
}
return $this->response;
}
/**
* CURL write callback
*
* @param resource &$curl CURL resource
* @param string &$data Data
* @return integer
*/
private function __responseWriteCallback(&$curl, &$data) {
$this->response->body .= $data;
return strlen($data);
}
/**
* Contributed by afx114
* URL encode the parameters as per http://docs.amazonwebservices.com/AWSECommerceService/latest/DG/index.html?Query_QueryAuth.html
* PHP's rawurlencode() follows RFC 1738, not RFC 3986 as required by Amazon. The only difference is the tilde (~), so convert it back after rawurlencode
* See: http://www.morganney.com/blog/API/AWS-Product-Advertising-API-Requires-a-Signed-Request.php
*
* @param string $var String to encode
* @return string
*/
private function __customUrlEncode($var) {
return str_replace('%7E', '~', rawurlencode($var));
}
/**
* Generate the auth string using Hmac-SHA256
*
* @internal Used by SimpleDBRequest::getResponse()
* @param string $string String to sign
* @return string
*/
private function __getSignature($string) {
return base64_encode(hash_hmac('sha256', $string, $this->ses->getSecretKey(), true));
}
}
final class SimpleEmailServiceMessage {
// these are public for convenience only
// these are not to be used outside of the SimpleEmailService class!
public $to, $cc, $bcc, $replyto;
public $from, $returnpath;
public $subject, $messagetext, $messagehtml;
public $subjectCharset, $messageTextCharset, $messageHtmlCharset;
function __construct() {
$to = array();
$cc = array();
$bcc = array();
$replyto = array();
$from = null;
$returnpath = null;
$subject = null;
$messagetext = null;
$messagehtml = null;
$subjectCharset = null;
$messageTextCharset = null;
$messageHtmlCharset = null;
}
/**
* addTo, addCC, addBCC, and addReplyTo have the following behavior:
* If a single address is passed, it is appended to the current list of addresses.
* If an array of addresses is passed, that array is merged into the current list.
*/
function addTo($to) {
if(!is_array($to)) {
$this->to[] = $to;
}
else {
$this->to = array_merge($this->to, $to);
}
}
function addCC($cc) {
if(!is_array($cc)) {
$this->cc[] = $cc;
}
else {
$this->cc = array_merge($this->cc, $cc);
}
}
function addBCC($bcc) {
if(!is_array($bcc)) {
$this->bcc[] = $bcc;
}
else {
$this->bcc = array_merge($this->bcc, $bcc);
}
}
function addReplyTo($replyto) {
if(!is_array($replyto)) {
$this->replyto[] = $replyto;
}
else {
$this->replyto = array_merge($this->replyto, $replyto);
}
}
function setFrom($from) {
$this->from = $from;
}
function setReturnPath($returnpath) {
$this->returnpath = $returnpath;
}
function setSubject($subject) {
$this->subject = $subject;
}
function setSubjectCharset($charset) {
$this->subjectCharset = $charset;
}
function setMessageFromString($text, $html = null) {
$this->messagetext = $text;
$this->messagehtml = $html;
}
function setMessageFromFile($textfile, $htmlfile = null) {
if(file_exists($textfile) && is_file($textfile) && is_readable($textfile)) {
$this->messagetext = file_get_contents($textfile);
}
if(file_exists($htmlfile) && is_file($htmlfile) && is_readable($htmlfile)) {
$this->messagehtml = file_get_contents($htmlfile);
}
}
function setMessageFromURL($texturl, $htmlurl = null) {
$this->messagetext = file_get_contents($texturl);
if($htmlurl !== null) {
$this->messagehtml = file_get_contents($htmlurl);
}
}
function setMessageCharset($textCharset, $htmlCharset = null) {
$this->messageTextCharset = $textCharset;
$this->messageHtmlCharset = $htmlCharset;
}
/**
* Validates whether the message object has sufficient information to submit a request to SES.
* This does not guarantee the message will arrive, nor that the request will succeed;
* instead, it makes sure that no required fields are missing.
*
* This is used internally before attempting a SendEmail or SendRawEmail request,
* but it can be used outside of this file if verification is desired.
* May be useful if e.g. the data is being populated from a form; developers can generally
* use this function to verify completeness instead of writing custom logic.
*
* @return boolean
*/
public function validate() {
if(count($this->to) == 0)
return false;
if($this->from == null || strlen($this->from) == 0)
return false;
if($this->messagetext == null)
return false;
return true;
}
}
/**
* Thrown by SimpleEmailService when errors occur if you call
* enableUseExceptions(true).
*/
final class SimpleEmailServiceException extends Exception {
}

14
externals/balanced-php/.gitignore vendored Normal file
View File

@@ -0,0 +1,14 @@
# composer
.buildpath
composer.lock
composer.phar
vendor
*~
*#
# phar
*.phar
# eclipse-pdt
.settings
.project
*.iml

8
externals/balanced-php/.travis.yml vendored Normal file
View File

@@ -0,0 +1,8 @@
language: php
before_script:
- curl -s http://getcomposer.org/installer | php
- php composer.phar install
script: phpunit --bootstrap vendor/autoload.php --exclude-group suite tests/
php:
- 5.3
- 5.4

22
externals/balanced-php/LICENSE vendored Normal file
View File

@@ -0,0 +1,22 @@
Copyright (c) 2012 Balanced
MIT License
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

156
externals/balanced-php/README.md vendored Normal file
View File

@@ -0,0 +1,156 @@
# Balanced
Online Marketplace Payments
[![Build Status](https://secure.travis-ci.org/balanced/balanced-php.png)](http://travis-ci.org/balanced/balanced-php)
The design of this library was heavily influenced by [Httpful](https://github.com/nategood/httpful).
## Requirements
- [PHP](http://www.php.net) >= 5.3 **with** [cURL](http://www.php.net/manual/en/curl.installation.php)
- [RESTful](https://github.com/bninja/restful) >= 0.1
- [Httpful](https://github.com/nategood/httpful) >= 0.1
## Issues
Please use appropriately tagged github [issues](https://github.com/balanced/balanced-php/issues) to request features or report bugs.
## Installation
You can install using [composer](#composer), a [phar](#phar) package or from [source](#source). Note that Balanced is [PSR-0](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-0.md) compliant:
### Composer
If you don't have Composer [install](http://getcomposer.org/doc/00-intro.md#installation) it:
$ curl -s https://getcomposer.org/installer | php
Add this to your `composer.json`:
{
"require": {
"balanced/balanced": "*"
}
}
Refresh your dependencies:
$ php composer.phar update
Then make sure to `require` the autoloader and initialize all:
<?php
require(__DIR__ . '/vendor/autoload.php');
\Httpful\Bootstrap::init();
\RESTful\Bootstrap::init();
\Balanced\Bootstrap::init();
...
### Phar
Download an Httpful [phar](http://php.net/manual/en/book.phar.php) file, which are all [here](https://github.com/nategood/httpful/downloads):
$ curl -s -L -o httpful.phar https://github.com/downloads/nategood/httpful/httpful.phar
Download a RESTful [phar](http://php.net/manual/en/book.phar.php) file, which are all [here](https://github.com/bninja/restful/downloads):
$ curl -s -L -o restful.phar https://github.com/bninja/restful/downloads/restful.phar
Download a Balanced [phar](http://php.net/manual/en/book.phar.php) file, which are all [here](https://github.com/balanced/balanced-php/downloads):
$ curl -s -L -o balanced.phar https://github.com/balanced/balanced-php/downloads/balanced-{VERSION}.phar
And then `include` all:
<?php
include(__DIR__ . '/httpful.phar');
include(__DIR__ . '/restful.phar');
include(__DIR__ . '/balanced.phar');
...
### Source
Download [Httpful](https://github.com/nategood/httpful) source:
$ curl -s -L -o httpful.zip https://github.com/nategood/httpful/zipball/master;
$ unzip httpful.zip; mv nategood-httpful* httpful; rm httpful.zip
Download [RESTful](https://github.com/bninja/restful) source:
$ curl -s -L -o restful.zip https://github.com/bninja/restful/zipball/master;
$ unzip restful.zip; mv bninja-restful* restful; rm restful.zips
Download the Balanced source:
$ curl -s -L -o balanced.zip https://github.com/balanced/balanced-php/zipball/master
$ unzip balanced.zip; mv balanced-balanced-php-* balanced; rm balanced.zip
And then `require` all bootstrap files:
<?php
require(__DIR__ . "/httpful/bootstrap.php")
require(__DIR__ . "/restful/bootstrap.php")
require(__DIR__ . "/balanced/bootstrap.php")
...
## Quickstart
curl -s http://getcomposer.org/installer | php
echo '{
"require": {
"balanced/balanced": "*"
}
}' > composer.json
php composer.phar install
curl https://raw.github.com/balanced/balanced-php/master/example/example.php > example.php
php example.php
curl https://raw.github.com/balanced/balanced-php/master/example/buyer-example.php > buyer-example.php
php -S 127.0.0.1:9321 buyer-example.php
# now open a browser and go to http://127.0.0.1:9321/ to view how to tokenize cards and add to a buyer
## Usage
See https://www.balancedpayments.com/docs/overview?language=php for tutorials and documentation.
## Testing
$ phpunit --bootstrap vendor/autoload.php tests/
Or if you'd like to skip network calls:
$ phpunit --exclude-group suite --bootstrap vendor/autoload.php tests/
## Publishing
1. Ensure that **all** [tests](#testing) pass
2. Increment minor `VERSION` in `src/Balanced/Settings` and `composer.json` (`git commit -am 'v{VERSION} release'`)
3. Tag it (`git tag -a v{VERSION} -m 'v{VERSION} release'`)
4. Push the tag (`git push --tag`)
5. [Packagist](http://packagist.org/packages/balanced/balanced) will see the new tag and take it from there
6. Build (`build-phar`) and upload a [phar](http://php.net/manual/en/book.phar.php) file
## Contributing
1. Fork it
2. Create your feature branch (`git checkout -b my-new-feature`)
3. Write your code **and [tests](#testing)**
4. Ensure all tests still pass (`phpunit --bootstrap vendor/autoload.php tests/`)
5. Commit your changes (`git commit -am 'Add some feature'`)
6. Push to the branch (`git push origin my-new-feature`)
7. Create new pull request
## Contributors
* [Jacob Rus](https://github.com/jrus)
* [Leon Smith](https://github.com/leonsmith)
* [Matt Drollette](https://github.com/MDrollette)
* [You](https://github.com/balanced/balanced-php/issues)!

4
externals/balanced-php/bootstrap.php vendored Normal file
View File

@@ -0,0 +1,4 @@
<?php
require(__DIR__ . '/src/Balanced/Bootstrap.php');
\Balanced\Bootstrap::init();

36
externals/balanced-php/build-phar vendored Executable file
View File

@@ -0,0 +1,36 @@
#!/usr/bin/php
<?php
include('src/Balanced/Settings.php');
function exit_unless($condition, $msg = null) {
if ($condition)
return;
echo "[FAIL] $msg";
exit(1);
}
echo "Building Phar... ";
$base_dir = dirname(__FILE__);
$source_dir = $base_dir . '/src/Balanced/';
$phar_name = 'balanced.phar';
$phar_path = $base_dir . '/' . $phar_name;
$phar = new Phar($phar_path, 0, $phar_name);
$stub = <<<HEREDOC
<?php
// Phar Stub File
Phar::mapPhar('balanced.phar');
include('phar://balanced.phar/Balanced/Bootstrap.php');
\Balanced\Bootstrap::pharInit();
__HALT_COMPILER();
HEREDOC;
$phar->setStub($stub);
exit_unless($phar, "Unable to create a phar. Make sure you have phar.readonly=0 set in your ini file.");
$phar->buildFromDirectory(dirname($source_dir));
echo "[ OK ]\n";
echo "Renaming Phar... ";
$phar_versioned_name = 'balanced-' . \Balanced\Settings::VERSION . '.phar';
$phar_versioned_path = $base_dir . '/' . $phar_versioned_name;
rename($phar_path, $phar_versioned_path);
echo "[ OK ]\n";

24
externals/balanced-php/composer.json vendored Normal file
View File

@@ -0,0 +1,24 @@
{
"name": "balanced/balanced",
"description": "Client for Balanced API",
"homepage": "http://github.com/balanced/balanced-php",
"license": "MIT",
"keywords": ["payments", "api"],
"version": "0.7.1",
"authors": [
{
"name": "Balanced",
"email": "dev@balancedpayments.com",
"homepage": "http://www.balancedpayments.com"
}
],
"require": {
"nategood/httpful": "*",
"bninja/restful": "*"
},
"autoload": {
"psr-0": {
"Balanced": "src/"
}
}
}

View File

@@ -0,0 +1,54 @@
<?php
//
// Learn how to authenticate a bank account so you can debit with it.
//
require(__DIR__ . '/vendor/autoload.php');
Httpful\Bootstrap::init();
RESTful\Bootstrap::init();
Balanced\Bootstrap::init();
// create a new marketplace
$key = new Balanced\APIKey();
$key->save();
Balanced\Settings::$api_key = $key->secret;
$marketplace = new Balanced\Marketplace();
$marketplace->save();
// create a bank account
$bank_account = $marketplace->createBankAccount("Jack Q Merchant",
"123123123",
"123123123"
);
$buyer = $marketplace->createAccount("buyer@example.org");
$buyer->addBankAccount($bank_account);
print("you can't debit from a bank account until you verify it\n");
try {
$buyer->debit(100);
} catch (Exception $e) {
printf("Debit failed, %s\n", $e->getMessage());
}
// authenticate
$verification = $bank_account->verify();
try {
$verification->confirm(1, 2);
} catch (Balanced\Errors\BankAccountVerificationFailure $e) {
printf('Authentication error , %s\n', $e->getMessage());
print("PROTIP: for TEST bank accounts the valid amount is always 1 and 1\n");
}
$verification->confirm(1, 1);
$debit = $buyer->debit(100);
printf("debited the bank account %s for %d cents\n",
$debit->source->uri,
$debit->amount
);
print("and there you have it");
?>

View File

@@ -0,0 +1,157 @@
<?php
require(__DIR__ . '/vendor/autoload.php');
Httpful\Bootstrap::init();
RESTful\Bootstrap::init();
Balanced\Bootstrap::init();
$API_KEY_SECRET = '5f4db668a5ec11e1b908026ba7e239a9';
$page = $_SERVER['REQUEST_URI'];
Balanced\Settings::$api_key = $API_KEY_SECRET;
$marketplace = Balanced\Marketplace::mine();
if ($page == '/') {
// do nothing
} elseif ($page == '/buyer') {
if (isset($_POST['uri']) and isset($_POST['email_address'])) {
// create in balanced
$email_address = $_POST['email_address'];
$card_uri = $_POST['uri'];
try {
echo create_buyer($email_address, $card_uri)->uri;
return;
} catch (Balanced\Errors\Error $e) {
echo $e->getMessage();
return;
}
}
}
function create_buyer($email_address, $card_uri) {
$marketplace = Balanced\Marketplace::mine();
try {
# new buyer
$buyer = $marketplace->createBuyer(
$email_address,
$card_uri);
}
catch (Balanced\Errors\DuplicateAccountEmailAddress $e) {
# oops, account for $email_address already exists so just add the card
$buyer = Balanced\Account::get($e->extras->account_uri);
$buyer->addCard($card_uri);
}
return $buyer;
}
?>
<html>
<head>
<link rel="stylesheet" href="http://twitter.github.com/bootstrap/assets/css/bootstrap.css" type="text/css">
<style type="text/css">
[name="marketplace_eid"] {
width: 300px;
}
[name^="expiration"] {
width: 50px;
}
[name="security_code"] {
width: 50px;
}
code { display: block; }
pre { color: green; }
</style>
</head>
<body>
<h1>Balanced Sample - Collect Credit Card Information</h1>
<div class="row">
<div class="span6">
<form id="payment">
<div>
<label>Email Address</label>
<input name="email_address" value="bob@example.com">
</div>
<div>
<label>Card Number</label>
<input name="card_number" value="4111111111111111" autocomplete="off">
</div>
<div>
<label>Expiration</label>
<input name="expiration_month" value="1"> / <input name="expiration_year" value="2020">
</div>
<div>
<label>Security Code</label>
<input name="security_code" value="123" autocomplete="off">
</div>
<button>Submit Payment Data</button>
</form>
</div>
</div>
<div id="result"></div>
<script type="text/javascript" src="https://js.balancedpayments.com/v1/balanced.js"></script>
<script type="text/javascript" src="//ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script>
<script type="text/javascript">
var marketplaceUri = '<?php echo $marketplace->uri; ?>';
var debug = function (tag, content) {
$('<' + tag + '>' + content + '</' + tag + '>').appendTo('#result');
};
try {
balanced.init(marketplaceUri);
} catch (e) {
debug('code', 'You need to set the marketplaceUri variable');
}
function accountCreated(response) {
debug('code', 'account create result: ' + response);
}
function balancedCallback(response) {
var tag = (response.status < 300) ? 'pre' : 'code';
debug(tag, JSON.stringify(response));
switch (response.status) {
case 201:
// response.data.uri == uri of the card resource, submit to your server
$.post('/buyer', {
uri: response.data.uri,
email_address: $('[name="email_address"]').val()
}, accountCreated);
case 400:
case 403:
// missing/malformed data - check response.error for details
break;
case 402:
// we couldn't authorize the buyer's credit card - check response.error for details
break;
case 404:
// your marketplace URI is incorrect
break;
default:
// we did something unexpected - check response.error for details
break;
}
}
var tokenizeCard = function(e) {
e.preventDefault();
var $form = $('form#payment');
var cardData = {
card_number: $form.find('[name="card_number"]').val(),
expiration_month: $form.find('[name="expiration_month"]').val(),
expiration_year: $form.find('[name="expiration_year"]').val(),
security_code: $form.find('[name="security_code"]').val()
};
balanced.card.create(cardData, balancedCallback);
};
$('#payment').submit(tokenizeCard);
if (window.location.protocol === 'file:') {
alert("balanced.js does not work when included in pages served over file:// URLs. Try serving this page over a webserver. Contact support@balancedpayments.com if you need assistance.");
}
</script>
</body>
</html>

View File

@@ -0,0 +1,5 @@
{
"require": {
"balanced/balanced": "*"
}
}

View File

@@ -0,0 +1,42 @@
<?php
require('vendor/autoload.php');
Httpful\Bootstrap::init();
RESTful\Bootstrap::init();
Balanced\Bootstrap::init();
$API_KEY_SECRET = '5f4db668a5ec11e1b908026ba7e239a9';
Balanced\Settings::$api_key = $API_KEY_SECRET;
$marketplace = Balanced\Marketplace::mine();
print "create a card\n";
$card = $marketplace->cards->create(array(
"card_number" => "5105105105105100",
"expiration_month" => "12",
"expiration_year" => "2015"
));
print "our card: " . $card->uri . "\n";
print "create a **buyer** account with that card\n";
$buyer = $marketplace->createBuyer(null, $card->uri);
print "our buyer account: " . $buyer->uri . "\n";
print "debit our buyer, let's say $15\n";
try {
$debit = $buyer->debit(1500);
print "our buyer debit: " . $debit->uri . "\n";
}
catch (Balanced\Errors\Declined $e) {
print "oh no, the processor declined the debit!\n";
}
catch (Balanced\Errors\NoFundingSource $e) {
print "oh no, the buyer has not active funding sources!\n";
}
catch (Balanced\Errors\CannotDebit $e) {
print "oh no, the buyer has no debitable funding sources!\n";
}
print "and there you have it 8)\n";
?>

View File

@@ -0,0 +1,59 @@
<?php
/*
* Welcome weary traveller. Sick of polling for state changes? Well today have
* I got good news for you. Run this example below to see how to get yourself
* some callback goodness and to understand how events work.
*/
require(__DIR__ . "/vendor/autoload.php");
Httpful\Bootstrap::init();
RESTful\Bootstrap::init();
Balanced\Bootstrap::init();
// create a new marketplace
$key = new Balanced\APIKey();
$key->save();
Balanced\Settings::$api_key = $key->secret;
$marketplace = new Balanced\Marketplace();
$marketplace->save();
// let"s create a requestb.in
$ch = curl_init("http://requestb.in/api/v1/bins");
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_HTTPHEADER, array(
'Content-Type: application/json',
'Content-Length: ' . 0)
);
$result = json_decode(curl_exec($ch));
$bin_name = $result->name;
$callback_url = "http://requestb.in/" . $bin_name;
$requests_url = "http://requestb.in/api/v1/bins/" . $bin_name . "/requests";
printf("let's create a callback\n");
$marketplace->createCallback($callback_url);
printf("let's create a card and associate it with a new account\n");
$card = $marketplace->cards->create(array(
"card_number" => "5105105105105100",
"expiration_month" => "12",
"expiration_year" => "2015"
));
$buyer = $marketplace->createBuyer("buyer@example.org", $card->uri);
printf("generate a debit (which implicitly creates and captures a hold)\n");
$buyer->debit(100);
foreach ($marketplace->events as $event) {
printf("this was a %s event, it occurred at %s\n",
$event->type,
$event->occurred_at
);
}
printf("ok, let's check with requestb.in to see if our callbacks fired at %s\n", $callback_url);
printf("we received callbacks, you can view them at http://requestb.in/%s?inspect\n",
$bin_name
);
?>

View File

@@ -0,0 +1,120 @@
<?php
require('vendor/autoload.php');
Httpful\Bootstrap::init();
RESTful\Bootstrap::init();
Balanced\Bootstrap::init();
print "create our new api key\n";
$key = new Balanced\APIKey();
$key->save();
print "Our secret is " . $key->secret . "\n";
print "configure with our secret " . $key->secret . "\n";
Balanced\Settings::$api_key = $key->secret;
print "create our marketplace";
$marketplace = new Balanced\Marketplace();
$marketplace->save();
if (Balanced\Merchant::me() == null) {
throw new Exception("Balanced\Merchant::me() should not be null");
}
print "What's my merchant? Easy: Balanced\Merchant::me(): " . Balanced\Merchant::me()->uri . "\n";
if (Balanced\Marketplace::mine() == null) {
throw new Exception("Balanced\Marketplace::mine() should never be null");
}
print "What's my marketplace? Easy: Balanced\Marketplace::mine(): " .Balanced\Marketplace::mine()->uri . "\n";
print "My marketplace's name is " . $marketplace->name . "\n";
print "Changing it to TestFooey\n";
$marketplace->name = "TestFooey";
$marketplace->save();
print "My marketplace name is now " . $marketplace->name . "\n";
if ($marketplace->name != "TestFooey") {
throw new Exception("Marketplace name is NOT TestFooey");
}
print "Cool, let's create a card\n";
$card = $marketplace->cards->create(array(
"card_number" => "5105105105105100",
"expiration_month" => "12",
"expiration_year" => "2015"
));
print "Our card: " . $card->uri . "\n";
print "Create out **buyer** account\n";
$buyer = $marketplace->createBuyer("buyer@example.org", $card->uri);
print "our buyer account: " . $buyer->uri . "\n";
print "hold some amount of funds on the buyer, let's say $15\n";
$the_hold = $buyer->hold(1500);
print "ok, no more holds! let's capture it (for the full amount)\n";
$debit = $the_hold->capture();
print "hmm, ho much money do i have in escrow? it should equal the debit amount\n";
$marketplace = Balanced\Marketplace::mine();
if ($marketplace->in_escrow != 1500) {
throw new Exception("1500 is not in escrow! This is wrong");
}
print "I have " . $marketplace->in_escrow . " in escrow!\n";
print "Cool. now let me refund the full amount";
$refund = $debit->refund();
print "ok, we have a merchant that's signing up, let's create an account for them first, let's create their bank account\n";
$bank_account = $marketplace->createBankAccount("Jack Q Merchant",
"123123123", /* account_number */
"123123123" /* bank_code (routing number is USA)*/
);
$identity = array(
"type" => "person",
"name" => "Billy Jones",
"street_address" => "801 High St",
"postal_code" => "94301",
"country" => "USA",
"dob" => "1979-02",
"phone_number" => "+16505551234"
);
$merchant = $marketplace->createMerchant('merchant@example.org',
$identity,
$bank_account->uri
);
print "our buyer is interested in buying something for $130\n";
$another_debit = $buyer->debit(13000, "MARKETPLACE.COM");
print "let's credit our merchant $110\n";
$credit = $merchant->credit(11000, "Buyer purchase something on Marketplace.com");
print "let's assume the marketplace charges 15%, so it earned $20\n";
$mp_credit = $marketplace->owner_account->credit(2000,
"Commission from MARKETPLACE.COM");
print "ok, let's invalidate the card used so it cannot be used again\n";
$card->is_valid = false;
$card->save();
print "how do we look up an existing object from the URI?\n";
$the_buyer = Balanced\Account::get($buyer->uri);
print "we got the buyer " . $the_buyer->email_address . "\n";
$the_debit = Balanced\Debit::get($debit->uri);
print "we got the debit: " . $the_debit->uri . "\n";
$the_credit = Balanced\Credit::get($credit->uri);
print "we got the credit: " . $the_credit->uri . "\n";
print "and there you have it :)\n";
?>

View File

@@ -0,0 +1,71 @@
<?
require('vendor/autoload.php');
Httpful\Bootstrap::init();
RESTful\Bootstrap::init();
Balanced\Bootstrap::init();
$key = new Balanced\APIKey();
$key->save();
Balanced\Settings::$api_key = $key->secret;
$marketplace = new Balanced\Marketplace();
$marketplace->save();
$card = $marketplace->cards->create(array(
"card_number" => "5105105105105100",
"expiration_month" => "12",
"expiration_year" => "2015"
));
$buyer = $marketplace->createBuyer("buyer@example.com", $card->uri);
$debit = $buyer->debit(1500);
$debit->refund(100);
$debit->refund(100);
$debit->refund(100);
echo $debit->refunds->total() . " refunds" . "\n";
$total = 0;
foreach ($debit->refunds as $r) {
$total += $r->amount;
print "refund = " . $r->amount . "\n";
}
print $total . "\n";
# bigger pagination example
print "Create 60 **buyer** with cards accounts\n";
for ($i = 0; $i < 60; $i++) {
$card = $marketplace->cards->create(array(
"card_number" => "5105105105105100",
"expiration_month" => "12",
"expiration_year" => "2015"
));
$buyer = $marketplace->createBuyer("buyer" . $i . "@example.org", $card->uri);
print '.';
}
print "\n";
$cards = $marketplace->cards;
print $cards->total() . " cards in Marketplace\n";
foreach ($cards as $c) {
print "card " . $c->uri . "\n";
}
# let's iterate through cards for just a single account
foreach ($buyer->cards as $c) {
print "buyer's card " . $c->uri . "\n";
}
print "and there you have it :)\n";
?>

View File

@@ -0,0 +1,14 @@
<?php
// run this file to test your composer install of Balanced
require(__DIR__ . '/vendor/autoload.php');
\Httpful\Bootstrap::init();
\RESTful\Bootstrap::init();
\Balanced\Bootstrap::init();
echo "[ OK ]\n";
echo "balanced version -- " . \Balanced\Settings::VERSION . " \n";
echo "restful version -- " . \RESTful\Settings::VERSION . " \n";
echo "httpful version -- " . \Httpful\Httpful::VERSION . " \n";

View File

@@ -0,0 +1,12 @@
<?php
// run this file to test your phar install of Balanced
include(__DIR__ . '/httpful.phar');
include(__DIR__ . '/restful.phar');
include(__DIR__ . '/balanced.phar');
echo "[ OK ]\n";
echo "balanced version -- " . \Balanced\Settings::VERSION . " \n";
echo "restful version -- " . \RESTful\Settings::VERSION . " \n";
echo "httpful version -- " . \Httpful\Httpful::VERSION . " \n";

View File

@@ -0,0 +1,12 @@
<?php
// run this file to test your source install of Balanced
require(__DIR__ . "/httpful/bootstrap.php");
require(__DIR__ . "/restful/bootstrap.php");
require(__DIR__ . "/balanced/bootstrap.php");
echo "[ OK ]\n";
echo "balanced version -- " . \Balanced\Settings::VERSION . " \n";
echo "restful version -- " . \RESTful\Settings::VERSION . " \n";
echo "httpful version -- " . \Httpful\Httpful::VERSION . " \n";

View File

@@ -0,0 +1,55 @@
<?php
namespace Balanced;
use Balanced\Resource;
use Balanced\Settings;
use \RESTful\URISpec;
/**
* Represents an api key. These are used to authenticate you with the api.
*
* Typically you create an initial api key:
*
* <code>
* print \Balanced\Settings::$api_key == null;
* $api_key = new \Balanced\APIKey();
* $api_key = api_key->save();
* $secret = $api_key->secret;
* print $secret;
* </code>
*
* Then save the returned secret (we don't store it) and configure the client
* to use it:
*
* <code>
* \Balanced\Settings::$api_key = 'my-api-key-secret';
* </code>
*
* You can later add another api key if you'd like to rotate or expire old
* ones:
*
* <code>
* $api_key = new \Balanced\APIKey();
* $api_key = api_key->save();
* $new_secret = $api_key->secret;
* print $new_secret;
*
* \Balanced\Settings::$api_key = $new_secret;
*
* \Balanced\APIKey::query()
* ->sort(\Balanced\APIKey::f->created_at->desc())
* ->first()
* ->delete();
* </code>
*/
class APIKey extends Resource
{
protected static $_uri_spec = null;
public static function init()
{
self::$_uri_spec = new URISpec('api_keys', 'id', '/v1');
self::$_registry->add(get_called_class());
}
}

View File

@@ -0,0 +1,217 @@
<?php
namespace Balanced;
use Balanced\Resource;
use \RESTful\URISpec;
/**
* Represent a buyer or merchant account on a marketplace.
*
* You create these using Balanced\Marketplace->createBuyer or
* Balanced\Marketplace->createMerchant.
*
* <code>
* $marketplace = \Balanced\Marketplace::mine();
*
* $card = $marketplace->cards->create(array(
* 'street_address' => $street_address,
* 'city' => 'Jollywood',
* 'region' => 'CA',
* 'postal_code' => '90210',
* 'name' => 'Captain Chunk',
* 'card_number' => '4111111111111111',
* 'expiration_month' => 7,
* 'expiration_year' => 2015
* ));
*
* $buyer = $marketplace->createBuyer(
* 'buyer@example.com',
* $card->uri,
* array(
* 'my_id' => '1212121',
* )
* );
* </code>
*
* @see Balanced\Marketplace->createBuyer
* @see Balanced\Marketplace->createMerchant
*/
class Account extends Resource
{
protected static $_uri_spec = null;
public static function init()
{
self::$_uri_spec = new URISpec('accounts', 'id');
self::$_registry->add(get_called_class());
}
/**
* Credit the account.
*
* @param int amount Amount to credit the account in USD pennies.
* @param string description Optional description of the credit.
* @param array[string]string meta Optional metadata to associate with the credit.
* @param mixed destination Optional URI of a funding destination (i.e. \Balanced\BankAccount) associated with this account to credit. If not specified the funding destination most recently added to the account is used.
* @param string appears_on_statement_as Optional description of the credit as it will appears on the customer's billing statement.
*
* @return \Balanced\Credit
*/
public function credit(
$amount,
$description = null,
$meta = null,
$destination = null,
$appears_on_statement_as = null)
{
if ($destination == null)
$destination_uri = null;
else
$destination_uri = is_string($destination) ? $destination : $destination->uri;
return $this->credits->create(array(
'amount' => $amount,
'description' => $description,
'meta' => $meta,
'destination_uri' => $destination_uri,
'appears_on_statement_as' => $appears_on_statement_as
));
}
/**
* Debit the account.
*
* @param int amount Amount to debit the account in USD pennies.
* @param string appears_on_statement_as Optional description of the debit as it will appears on the customer's billing statement.
* @param string description Optional description of the debit.
* @param array[string]string meta Optional metadata to associate with the debit.
* @param mixed Optional funding source (i.e. \Balanced\Card) or URI of a funding source associated with this account to debit. If not specified the funding source most recently added to the account is used.
*
* @return \Balanced\Debit
*/
public function debit(
$amount,
$appears_on_statement_as = null,
$description = null,
$meta = null,
$source = null,
$on_behalf_of = null)
{
if ($source == null) {
$source_uri = null;
} else if (is_string($source)) {
$source_uri = $source;
} else {
$source_uri = $source->uri;
}
if ($on_behalf_of == null) {
$on_behalf_of_uri = null;
} else if (is_string($on_behalf_of)) {
$on_behalf_of_uri = $on_behalf_of;
} else {
$on_behalf_of_uri = $on_behalf_of->uri;
}
if (isset($this->uri) && $on_behalf_of_uri == $this->uri)
throw new \InvalidArgumentException(
'The on_behalf_of parameter MAY NOT be the same account as the account you are debiting!'
);
return $this->debits->create(array(
'amount' => $amount,
'description' => $description,
'meta' => $meta,
'source_uri' => $source_uri,
'on_behalf_of_uri' => $on_behalf_of_uri,
'appears_on_statement_as' => $appears_on_statement_as
));
}
/**
* Create a hold (i.e. a guaranteed pending debit) for account funds. You
* can later capture or void. A hold is associated with a account funding
* source (i.e. \Balanced\Card). If you don't specify the source then the
* current primary funding source for the account is used.
*
* @param int amount Amount of the hold in USD pennies.
* @param string Optional description Description of the hold.
* @param string Optional URI referencing the card to use for the hold.
* @param array[string]string meta Optional metadata to associate with the hold.
*
* @return \Balanced\Hold
*/
public function hold(
$amount,
$description = null,
$source_uri = null,
$meta = null)
{
return $this->holds->create(array(
'amount' => $amount,
'description' => $description,
'source_uri' => $source_uri,
'meta' => $meta
));
}
/**
* Creates or associates a created card with the account. The default
* funding source for the account will be this card.
*
* @see \Balanced\Marketplace->createCard
*
* @param mixed card \Balanced\Card or URI referencing a card to associate with the account. Alternatively it can be an associative array describing a card to create and associate with the account.
*
* @return \Balanced\Account
*/
public function addCard($card)
{
if (is_string($card))
$this->card_uri = $card;
else if (is_array($card))
$this->card = $card;
else
$this->card_uri = $card->uri;
return $this->save();
}
/**
* Creates or associates a created bank account with the account. The
* new default funding destination for the account will be this bank account.
*
* @see \Balanced\Marketplace->createBankAccount
*
* @param mixed bank_account \Balanced\BankAccount or URI for a bank account to associate with the account. Alternatively it can be an associative array describing a bank account to create and associate with the account.
*
* @return \Balanced\Account
*/
public function addBankAccount($bank_account)
{
if (is_string($bank_account))
$this->bank_account_uri = $bank_account;
else if (is_array($bank_account))
$this->bank_account = $bank_account;
else
$this->bank_account_uri = $bank_account->uri;
return $this->save();
}
/**
* Promotes a role-less or buyer account to a merchant.
*
* @see Balanced\Marketplace::createMerchant
*
* @param mixed merchant Associative array describing the merchants identity or a URI referencing a created merchant.
*
* @return \Balanced\Account
*/
public function promoteToMerchant($merchant)
{
if (is_string($merchant))
$this->merchant_uri = $merchant;
else
$this->merchant = $merchant;
return $this->save();
}
}

View File

@@ -0,0 +1,127 @@
<?php
namespace Balanced;
use Balanced\Resource;
use \RESTful\URISpec;
/**
* Represents an account bank account.
*
* You can create these via Balanced\Marketplace::bank_accounts::create or
* Balanced\Marketplace::createBankAccount. Associate them with a buyer or
* merchant one creation via Balanced\Marketplace::createBuyer or
* Balanced\Marketplace::createMerchant and with an existing buyer or merchant
* use Balanced\Account::addBankAccount.
*
* <code>
* $marketplace = \Balanced\Marketplace::mine();
*
* $bank_account = $marketplace->bank_accounts->create(array(
* 'name' => 'name',
* 'account_number' => '11223344',
* 'bank_code' => '1313123',
* ));
*
* $account = $marketplace
* ->accounts
* ->query()
* ->filter(Account::f->email_address->eq('merchant@example.com'))
* ->one();
* $account->addBankAccount($bank_account->uri);
* </code>
*/
class BankAccount extends Resource
{
protected static $_uri_spec = null;
public static function init()
{
self::$_uri_spec = new URISpec('bank_accounts', 'id', '/v1');
self::$_registry->add(get_called_class());
}
/**
* Credit a bank account.
*
* @param int amount Amount to credit in USD pennies.
* @param string description Optional description of the credit.
* @param string appears_on_statement_as Optional description of the credit as it will appears on the customer's billing statement.
*
* @return \Balanced\Credit
*
* <code>
* $bank_account = new \Balanced\BankAccount(array(
* 'account_number' => '12341234',
* 'name' => 'Fit Finlay',
* 'bank_code' => '325182797',
* 'type' => 'checking',
* ));
*
* $credit = $bank_account->credit(123, 'something descriptive');
* </code>
*/
public function credit(
$amount,
$description = null,
$meta = null,
$appears_on_statement_as = null)
{
if (!property_exists($this, 'account') || $this->account == null) {
$credit = $this->credits->create(array(
'amount' => $amount,
'description' => $description,
));
} else {
$credit = $this->account->credit(
$amount,
$description,
$meta,
$this->uri,
$appears_on_statement_as
);
}
return $credit;
}
public function verify()
{
$response = self::getClient()->post(
$this->verifications_uri, null
);
$verification = new BankAccountVerification();
$verification->_objectify($response->body);
return $verification;
}
}
/**
* Represents an verification for a bank account which is a pre-requisite if
* you want to create debits using the associated bank account. The side-effect
* of creating a verification is that 2 random amounts will be deposited into
* the account which must then be confirmed via the confirm method to ensure
* that you have access to the bank account in question.
*
* You can create these via Balanced\Marketplace::bank_accounts::verify.
*
* <code>
* $marketplace = \Balanced\Marketplace::mine();
*
* $bank_account = $marketplace->bank_accounts->create(array(
* 'name' => 'name',
* 'account_number' => '11223344',
* 'bank_code' => '1313123',
* ));
*
* $verification = $bank_account->verify();
* </code>
*/
class BankAccountVerification extends Resource {
public function confirm($amount1, $amount2) {
$this->amount_1 = $amount1;
$this->amount_2 = $amount2;
$this->save();
return $this;
}
}

View File

@@ -0,0 +1,79 @@
<?php
namespace Balanced;
/**
* Bootstrapper for Balanced does autoloading and resource initialization.
*/
class Bootstrap
{
const DIR_SEPARATOR = DIRECTORY_SEPARATOR;
const NAMESPACE_SEPARATOR = '\\';
public static $initialized = false;
public static function init()
{
spl_autoload_register(array('\Balanced\Bootstrap', 'autoload'));
self::initializeResources();
}
public static function autoload($classname)
{
self::_autoload(dirname(dirname(__FILE__)), $classname);
}
public static function pharInit()
{
spl_autoload_register(array('\Balanced\Bootstrap', 'pharAutoload'));
self::initializeResources();
}
public static function pharAutoload($classname)
{
self::_autoload('phar://balanced.phar', $classname);
}
private static function _autoload($base, $classname)
{
if (!strncmp($classname, 'Balanced\Errors\\', strlen('Balanced\Errors\\')))
$classname = 'Balanced\Errors';
$parts = explode(self::NAMESPACE_SEPARATOR, $classname);
$path = $base . self::DIR_SEPARATOR. implode(self::DIR_SEPARATOR, $parts) . '.php';
if (file_exists($path)) {
require_once($path);
}
}
/**
* Initializes resources (i.e. registers them with Resource::_registry). Note
* that if you add a Resource then you must initialize it here.
*
* @internal
*/
private static function initializeResources()
{
if (self::$initialized)
return;
\Balanced\Errors\Error::init();
\Balanced\Resource::init();
\Balanced\APIKey::init();
\Balanced\Marketplace::init();
\Balanced\Account::init();
\Balanced\Credit::init();
\Balanced\Debit::init();
\Balanced\Refund::init();
\Balanced\Card::init();
\Balanced\BankAccount::init();
\Balanced\Hold::init();
\Balanced\Merchant::init();
\Balanced\Callback::init();
\Balanced\Event::init();
self::$initialized = true;
}
}

View File

@@ -0,0 +1,24 @@
<?php
namespace Balanced;
use Balanced\Resource;
use \RESTful\URISpec;
/*
* A Callback is a publicly accessible location that can receive POSTed JSON
* data whenever an Event is generated.
*
* You create these using Balanced\Marketplace->createCallback.
*
*/
class Callback extends Resource
{
protected static $_uri_spec = null;
public static function init()
{
self::$_uri_spec = new URISpec('callbacks', 'id');
self::$_registry->add(get_called_class());
}
}

View File

@@ -0,0 +1,61 @@
<?php
namespace Balanced;
use Balanced\Resource;
use \RESTful\URISpec;
/**
* Represents an account card.
*
* You can create these via Balanced\Marketplace::cards::create or
* Balanced\Marketplace::createCard. Associate them with a buyer or merchant
* one creation via Marketplace::createBuyer or
* Balanced\Marketplace::createMerchant and with an existing buyer or merchant
* use Balanced\Account::addCard.
*
* <code>
* $marketplace = \Balanced\Marketplace::mine();
*
* $card = $marketplace->cards->create(array(
* 'name' => 'name',
* 'account_number' => '11223344',
* 'bank_code' => '1313123'
* ));
*
* $account = $marketplace
* ->accounts
* ->query()
* ->filter(Account::f->email_address->eq('buyer@example.com'))
* ->one();
* $account->addCard($card->uri);
* </code>
*/
class Card extends Resource
{
protected static $_uri_spec = null;
public static function init()
{
self::$_uri_spec = new URISpec('cards', 'id', '/v1');
self::$_registry->add(get_called_class());
}
public function debit(
$amount,
$appears_on_statement_as = null,
$description = null,
$meta = null,
$source = null)
{
if ($this->account == null) {
throw new \UnexpectedValueException('Card is not associated with an account.');
}
return $this->account->debit(
$amount,
$appears_on_statement_as,
$description,
$meta,
$this->uri);
}
}

View File

@@ -0,0 +1,75 @@
<?php
namespace Balanced;
use Balanced\Resource;
use \RESTful\URISpec;
/**
* Represents an account credit transaction.
*
* You create these using Balanced\Account::credit.
*
* <code>
* $marketplace = \Balanced\Marketplace::mine();
*
* $account = $marketplace
* ->accounts
* ->query()
* ->filter(Account::f->email_address->eq('merchant@example.com'))
* ->one();
*
* $credit = $account->credit(
* 100,
* 'how it '
* array(
* 'my_id': '112233'
* )
* );
* </code>
*/
class Credit extends Resource
{
protected static $_uri_spec = null;
public static function init()
{
self::$_uri_spec = new URISpec('credits', 'id', '/v1');
self::$_registry->add(get_called_class());
}
/**
* Credit an unstored bank account.
*
* @param int amount Amount to credit in USD pennies.
* @param string description Optional description of the credit.
* @param mixed bank_account Associative array describing a bank account to credit. The bank account will *not* be stored.
*
* @return \Balanced\Credit
*
* <code>
* $credit = \Balanced\Credit::bankAccount(
* 123,
* array(
* 'account_number' => '12341234',
* 'name' => 'Fit Finlay',
* 'bank_code' => '325182797',
* 'type' => 'checking',
* ),
* 'something descriptive');
* </code>
*/
public static function bankAccount(
$amount,
$bank_account,
$description = null)
{
$credit = new Credit(array(
'amount' => $amount,
'bank_account' => $bank_account,
'description' => $description
));
$credit->save();
return $credit;
}
}

View File

@@ -0,0 +1,64 @@
<?php
namespace Balanced;
use Balanced\Resource;
use \RESTful\URISpec;
/**
* Represents an account debit transaction.
*
* You create these using Balanced\Account::debit.
*
* <code>
* $marketplace = \Balanced\Marketplace::mine();
*
* $account = $marketplace
* ->accounts
* ->query()
* ->filter(Account::f->email_address->eq('buyer@example.com'))
* ->one();
*
* $debit = $account->debit(
* 100,
* 'how it appears on the statement',
* 'a description',
* array(
* 'my_id': '443322'
* )
* );
* </code>
*/
class Debit extends Resource
{
protected static $_uri_spec = null;
public static function init()
{
self::$_uri_spec = new URISpec('debits', 'id');
self::$_registry->add(get_called_class());
}
/**
* Create a refund for this debit. You can create multiple refunds for a
* debit but the total amount of the refunds must be less than the debit
* amount.
*
* @param int amount Optional amount of the refund in USD pennies. If unspecified then the full debit amount is used.
* @param string description Optional description of the refund.
* @param array[string]string meta Optional metadata to associate with the refund.
*
* @return \Balanced\Refund
*/
public function refund(
$amount = null,
$description = null,
$meta = null)
{
return $this->refunds->create(array(
'amount' => $amount,
'description' => $description,
'meta' => $meta
));
}
}

View File

@@ -0,0 +1,135 @@
<?php
namespace Balanced\Errors;
use RESTful\Exceptions\HTTPError;
class Error extends HTTPError
{
public static $codes = array();
public static function init()
{
foreach (get_declared_classes() as $class) {
$parent_class = get_parent_class($class);
if ($parent_class != 'Balanced\Errors\Error')
continue;
foreach ($class::$codes as $type)
self::$codes[$type] = $class;
}
}
}
class DuplicateAccountEmailAddress extends Error
{
public static $codes = array('duplicate-email-address');
}
class InvalidAmount extends Error
{
public static $codes = array('invalid-amount');
}
class InvalidRoutingNumber extends Error
{
public static $codes = array('invalid-routing-number');
}
class InvalidBankAccountNumber extends Error
{
public static $codes = array('invalid-bank-account-number');
}
class Declined extends Error
{
public static $codes = array('funding-destination-declined', 'authorization-failed');
}
class CannotAssociateMerchantWithAccount extends Error
{
public static $codes = array('cannot-associate-merchant-with-account');
}
class AccountIsAlreadyAMerchant extends Error
{
public static $codes = array('account-already-merchant');
}
class NoFundingSource extends Error
{
public static $codes = array('no-funding-source');
}
class NoFundingDestination extends Error
{
public static $codes = array('no-funding-destination');
}
class CardAlreadyAssociated extends Error
{
public static $codes = array('card-already-funding-src');
}
class CannotAssociateCard extends Error
{
public static $codes = array('cannot-associate-card');
}
class BankAccountAlreadyAssociated extends Error
{
public static $codes = array('bank-account-already-associated');
}
class AddressVerificationFailed extends Error
{
public static $codes = array('address-verification-failed');
}
class HoldExpired extends Error
{
public static $codes = array('authorization-expired');
}
class MarketplaceAlreadyCreated extends Error
{
public static $codes = array('marketplace-already-created');
}
class IdentityVerificationFailed extends Error
{
public static $codes = array('identity-verification-error', 'business-principal-kyc', 'business-kyc', 'person-kyc');
}
class InsufficientFunds extends Error
{
public static $codes = array('insufficient-funds');
}
class CannotHold extends Error
{
public static $codes = array('funding-source-not-hold');
}
class CannotCredit extends Error
{
public static $codes = array('funding-destination-not-creditable');
}
class CannotDebit extends Error
{
public static $codes = array('funding-source-not-debitable');
}
class CannotRefund extends Error
{
public static $codes = array('funding-source-not-refundable');
}
class BankAccountVerificationFailure extends Error
{
public static $codes = array(
'bank-account-authentication-not-pending',
'bank-account-authentication-failed',
'bank-account-authentication-already-exists'
);
}

View File

@@ -0,0 +1,23 @@
<?php
namespace Balanced;
use Balanced\Resource;
use \RESTful\URISpec;
/*
* An Event is a snapshot of another resource at a point in time when
* something significant occurred. Events are created when resources are
* created, updated, deleted or otherwise change state such as a Credit
* being marked as failed.
*/
class Event extends Resource
{
protected static $_uri_spec = null;
public static function init()
{
self::$_uri_spec = new URISpec('events', 'id', '/v1');
self::$_registry->add(get_called_class());
}
}

View File

@@ -0,0 +1,77 @@
<?php
namespace Balanced;
use Balanced\Resource;
use \RESTful\URISpec;
/**
* Represents pending debit of funds for an account. The funds for that debit
* are held by the processor. You can later capture the hold, which results in
* debit, or void it, which releases the held funds.
*
* Note that a hold can expire so you should always check
* Balanced\Hold::expires_at.
*
* You create these using \Balanced\Account::hold.
*
* <code>
* $marketplace = \Balanced\Marketplace::mine();
*
* $account = $marketplace
* ->accounts
* ->query()
* ->filter(Account::f->email_address->eq('buyer@example.com'))
* ->one();
*
* $hold = $account->hold(
* 100,
* 'a description',
* null,
* array(
* 'my_id': '1293712837'
* )
* );
*
* $debit = $hold->capture();
* </code>
*/
class Hold extends Resource
{
protected static $_uri_spec = null;
public static function init()
{
self::$_uri_spec = new URISpec('holds', 'id');
self::$_registry->add(get_called_class());
}
/**
** Voids a pending hold. This releases the held funds. Once voided a hold
* is not longer pending can cannot be re-captured or re-voided.
*
* @return \Balanced\Hold
*/
public function void()
{
$this->is_void = true;
return $this->save();
}
/**
* Captures a pending hold. This results in a debit. Once captured a hold
* is not longer pending can cannot be re-captured or re-voided.
*
* @param int amount Optional Portion of the pending hold to capture. If not specified the full amount associated with the hold is captured.
*
* @return \Balanced\Debit
*/
public function capture($amount = null)
{
$this->debit = $this->account->debits->create(array(
'hold_uri' => $this->uri,
'amount' => $amount,
));
return $this->debit;
}
}

View File

@@ -0,0 +1,325 @@
<?php
namespace Balanced;
use Balanced\Resource;
use Balanced\Errors;
use Balanced\Account;
use \RESTful\URISpec;
/**
* Represents a marketplace.
*
* To get started you create an api key and then create a marketplace:
*
* <code>
* $api_key = new \Balanced\APIKey();
* $api_key->save();
* $secret = $api_key->secret // better save this somewhere
* print $secret;
* \Balanced\Settings::$api_key = $secret;
*
* $marketplace = new \Balanced\Marketplace();
* $marketplace->save();
* var_dump($marketplace);
* </code>
*
* Each api key is uniquely associated with an api key so once you've created a
* marketplace:
*
* <code>
* \Balanced\Settings::$api_key = $secret;
* $marketplace = \Balanced\Marketplace::mine(); // this is the marketplace associated with $secret
* </code>
*/
class Marketplace extends Resource
{
protected static $_uri_spec = null;
public static function init()
{
self::$_uri_spec = new URISpec('marketplaces', 'id', '/v1');
self::$_registry->add(get_called_class());
}
/**
* Get the marketplace associated with the currently configured
* \Balanced\Settings::$api_key.
*
* @throws \RESTful\Exceptions\NoResultFound
* @return \Balanced\Marketplace
*/
public static function mine()
{
return self::query()->one();
}
/**
* Create a card. These can later be associated with an account using
* \Balanced\Account->addCard or \Balanced\Marketplace->createBuyer.
*
* @param string street_address Street address. Use null if there is no address for the card.
* @param string city City. Use null if there is no address for the card.
* @param string postal_code Postal code. Use null if there is no address for the card.
* @param string name Name as it appears on the card.
* @param string card_number Card number.
* @param string security_code Card security code. Use null if it is no available.
* @param int expiration_month Expiration month.
* @param int expiration_year Expiration year.
*
* @return \Balanced\Card
*/
public function createCard(
$street_address,
$city,
$region,
$postal_code,
$name,
$card_number,
$security_code,
$expiration_month,
$expiration_year)
{
if ($region != null && strlen($region) > 0) {
trigger_error("The region parameter will be deprecated in the next minor version of balanced-php", E_USER_NOTICE);
}
return $this->cards->create(array(
'street_address' => $street_address,
'city' => $city,
'region' => $region,
'postal_code' => $postal_code,
'name' => $name,
'card_number' => $card_number,
'security_code' => $security_code,
'expiration_month' => $expiration_month,
'expiration_year' => $expiration_year
));
}
/**
* Create a bank account. These can later be associated with an account
* using \Balanced\Account->addBankAccount.
*
* @param string name Name of the account holder.
* @param string account_number Account number.
* @param string routing_number Bank code or routing number.
* @param string type checking or savings
* @param array meta Single level mapping from string keys to string values.
*
* @return \Balanced\BankAccount
*/
public function createBankAccount(
$name,
$account_number,
$routing_number,
$type,
$meta = null
)
{
return $this->bank_accounts->create(array(
'name' => $name,
'account_number' => $account_number,
'routing_number' => $routing_number,
'type' => $type,
'meta' => $meta
));
}
/**
* Create a role-less account. You can later turn this into a buyer by
* adding a funding source (e.g a card) or a merchant using
* \Balanced\Account->promoteToMerchant.
*
* @param string email_address Optional email address. There can only be one account with this email address.
* @param array[string]string meta Optional metadata to associate with the account.
*
* @return \Balanced\Account
*/
public function createAccount($email_address = null, $meta = null)
{
return $this->accounts->create(array(
'email_address' => $email_address,
'meta' => $meta,
));
}
/**
* Find or create a role-less account by email address. You can later turn
* this into a buyer by adding a funding source (e.g a card) or a merchant
* using \Balanced\Account->promoteToMerchant.
*
* @param string email_address Email address. There can only be one account with this email address.
*
* @return \Balanced\Account
*/
function findOrCreateAccountByEmailAddress($email_address)
{
$marketplace = Marketplace::mine();
try {
$account = $this->accounts->create(array(
'email_address' => $email_address
));
}
catch (Errors\DuplicateAccountEmailAddress $e) {
$account = Account::get($e->extras->account_uri);
}
return $account;
}
/**
* Create a buyer account.
*
* @param string email_address Optional email address. There can only be one account with this email address.
* @param string card_uri URI referencing a card to associate with the account.
* @param array[string]string meta Optional metadata to associate with the account.
* @param string name Optional name of the account.
*
* @return \Balanced\Account
*/
public function createBuyer($email_address, $card_uri, $meta = null, $name = null)
{
return $this->accounts->create(array(
'email_address' => $email_address,
'card_uri' => $card_uri,
'meta' => $meta,
'name' => $name
));
}
/**
* Create a merchant account.
*
* Unlike buyers the identity of a merchant must be established before
* the account can function as a merchant (i.e. be credited). A merchant
* can be either a person or a business. Either way that information is
* represented as an associative array and passed as the merchant parameter
* when creating the merchant account.
*
* For a person the array looks like this:
*
* <code>
* array(
* 'type' => 'person',
* 'name' => 'William James',
* 'tax_id' => '393-48-3992',
* 'street_address' => '167 West 74th Street',
* 'postal_code' => '10023',
* 'dob' => '1842-01-01',
* 'phone_number' => '+16505551234',
* 'country_code' => 'USA'
* )
* </code>
*
* For a business the array looks like this:
*
* <code>
* array(
* 'type' => 'business',
* 'name' => 'Levain Bakery',
* 'tax_id' => '253912384',
* 'street_address' => '167 West 74th Street',
* 'postal_code' => '10023',
* 'phone_number' => '+16505551234',
* 'country_code' => 'USA',
* 'person' => array(
* 'name' => 'William James',
* 'tax_id' => '393483992',
* 'street_address' => '167 West 74th Street',
* 'postal_code' => '10023',
* 'dob' => '1842-01-01',
* 'phone_number' => '+16505551234',
* 'country_code' => 'USA',
* )
* )
* </code>
*
* In some cases the identity of the merchant, person or business, cannot
* be verified in which case a \Balanced\Exceptions\HTTPError is thrown:
*
* <code>
* $identity = array(
* 'type' => 'business',
* 'name' => 'Levain Bakery',
* 'tax_id' => '253912384',
* 'street_address' => '167 West 74th Street',
* 'postal_code' => '10023',
* 'phone_number' => '+16505551234',
* 'country_code' => 'USA',
* 'person' => array(
* 'name' => 'William James',
* 'tax_id' => '393483992',
* 'street_address' => '167 West 74th Street',
* 'postal_code' => '10023',
* 'dob' => '1842-01-01',
* 'phone_number' => '+16505551234',
* 'country_code' => 'USA',
* ),
* );
*
* try {
* $merchant = \Balanced\Marketplace::mine()->createMerchant(
* 'merchant@example.com',
* $identity,
* );
* catch (\Balanced\Exceptions\HTTPError $e) {
* if ($e->code != 300) {
* throw $e;
* }
* print e->response->header['Location'] // this is where merchant must signup
* }
* </code>
*
* Once the merchant has completed signup you can use the resulting URI to
* create an account for them on your marketplace:
*
* <code>
* $merchant = self::$marketplace->createMerchant(
* 'merchant@example.com',
* null,
* null,
* $merchant_uri
* );
* </coe>
*
* @param string email_address Optional email address. There can only be one account with this email address.
* @param array[string]mixed merchant Associative array describing the merchants identity.
* @param string $bank_account_uri Optional URI referencing a bank account to associate with this account.
* @param string $merchant_uri URI of a merchant created via the redirection sign-up flow.
* @param string $name Optional name of the merchant.
* @param array[string]string meta Optional metadata to associate with the account.
*
* @return \Balanced\Account
*/
public function createMerchant(
$email_address = null,
$merchant = null,
$bank_account_uri = null,
$merchant_uri = null,
$name = null,
$meta = null)
{
return $this->accounts->create(array(
'email_address' => $email_address,
'merchant' => $merchant,
'merchant_uri' => $merchant_uri,
'bank_account_uri' => $bank_account_uri,
'name' => $name,
'meta' => $meta,
));
}
/*
* Create a callback.
*
* @param string url URL of callback.
*/
public function createCallback(
$url
)
{
return $this->callbacks->create(array(
'url' => $url
));
}
}

View File

@@ -0,0 +1,51 @@
<?php
namespace Balanced;
use Balanced\Resource;
use \RESTful\URISpec;
/**
* Represents a merchant identity.
*
* These are optionally created and associated with an account via
* \Balanced\Marketplace::createMerchant which establishes a merchant account
* on a marketplace.
*
* In some cases a merchant may need to be redirected to create a identity (e.g. the
* information provided cannot be verified, more information is needed, etc). That
* redirected signup results in a merchant_uri which is then associated with an
* account on the marketplace via \Balanced\Marketplace::createMerchant.
*
* @see \Balanced\Marketplace
*/
class Merchant extends Resource
{
protected static $_uri_spec = null;
public static function init()
{
self::$_uri_spec = new URISpec('merchants', 'id', '/v1');
self::$_registry->add(get_called_class());
}
/**
* Return the merchant identity associated with the current
* Balanced\Settings::$api_key. If you are not authenticated (i.e.
* ) then Balanced\Exceptions\NoResult
* will be thrown.
*
* <code>
* $merchant = \Balanced\Merchant::me();
* $owner_account = \Balanced\Marketplace::mine()->owner_account;
* assert($merchant->id == $owner_account->merchant->id);
* </code>
*
* @throws \RESTful\Exceptions\NoResultFound
* @return \Balanced\Merchant
*/
public static function me()
{
return self::query()->one();
}
}

View File

@@ -0,0 +1,50 @@
<?php
namespace Balanced;
use Balanced\Resource;
use \RESTful\URISpec;
/**
* Represents a refund of an account debit transaction.
*
* You create these via Balanced\Debit::refund.
*
* <code>
* $marketplace = \Balanced\Marketplace::mine();
*
* $account = $marketplace
* ->accounts
* ->query()
* ->filter(Account::f->email_address->eq('buyer@example.com'))
* ->one();
*
* $debit = $account->debit(
* 100,
* 'how it appears on the statement',
* 'a description',
* array(
* 'my_id': '443322'
* )
* );
*
* $debit->refund(
* 99,
* 'some description',
* array(
* 'my_id': '123123'
* )
* );
* </code>
*/
class Refund extends Resource
{
protected static $_uri_spec = null;
public static function init()
{
self::$_uri_spec = new URISpec('refunds', 'id');
self::$_registry->add(get_called_class());
}
}

View File

@@ -0,0 +1,48 @@
<?php
namespace Balanced;
use Balanced\Errors\Error;
use RESTful\Exceptions\HTTPError;
class Resource extends \RESTful\Resource
{
public static $fields, $f;
protected static $_client, $_registry, $_uri_spec;
public static function init()
{
self::$_client = new \RESTful\Client('\Balanced\Settings', null, __NAMESPACE__ .'\Resource::convertError');
self::$_registry = new \RESTful\Registry();
self::$f = self::$fields = new \RESTful\Fields();
}
public static function convertError($response)
{
if (property_exists($response->body, 'category_code') &&
array_key_exists($response->body->category_code, Error::$codes))
$error = new Error::$codes[$response->body->category_code]($response);
else
$error = new HTTPError($response);
return $error;
}
public static function getClient()
{
$class = get_called_class();
return $class::$_client;
}
public static function getRegistry()
{
$class = get_called_class();
return $class::$_registry;
}
public static function getURISpec()
{
$class = get_called_class();
return $class::$_uri_spec;
}
}

View File

@@ -0,0 +1,43 @@
<?php
namespace Balanced;
/**
* Configurable settings.
*
* You can either set these settings individually:
*
* <code>
* \Balanced\Settngs::api_key = 'my-api-key-secret';
* </code>
*
* or all at once:
*
* <code>
* \Balanced\Settngs::configure(
* 'https://api.balancedpayments.com',
* 'my-api-key-secret'
* );
* </code>
*/
class Settings
{
const VERSION = '0.7.1';
public static $url_root = 'https://api.balancedpayments.com',
$api_key = null,
$agent = 'balanced-php',
$version = Settings::VERSION;
/**
* Configure all settings.
*
* @param string url_root The root (schema://hostname[:port]) to use when constructing api URLs.
* @param string api_key The api key secret to use for authenticating when talking to the api. If null then api usage is limited to uauthenticated endpoints.
*/
public static function configure($url_root, $api_key)
{
self::$url_root= $url_root;
self::$api_key = $api_key;
}
}

View File

@@ -0,0 +1,614 @@
<?php
namespace Balanced\Test;
\Balanced\Bootstrap::init();
\RESTful\Bootstrap::init();
\Httpful\Bootstrap::init();
use Balanced\Resource;
use Balanced\Settings;
use Balanced\APIKey;
use Balanced\Marketplace;
use Balanced\Credit;
use Balanced\Debit;
use Balanced\Refund;
use Balanced\Account;
use Balanced\Merchant;
use Balanced\BankAccount;
use Balanced\Card;
use Balanced\Hold;
use \RESTful\Collection;
class APIKeyTest extends \PHPUnit_Framework_TestCase
{
function testRegistry()
{
$this->expectOutputString('');
$result = Resource::getRegistry()->match('/v1/api_keys');
return;
$expected = array(
'collection' => true,
'class' => 'Balanced\APIKey',
);
$this->assertEquals($expected, $result);
$result = Resource::getRegistry()->match('/v1/api_keys/1234');
$expected = array(
'collection' => false,
'class' => 'Balanced\APIKey',
'ids' => array('id' => '1234'),
);
$this->assertEquals($expected, $result);
}
}
class MarketplaceTest extends \PHPUnit_Framework_TestCase
{
function testRegistry()
{
$result = Resource::getRegistry()->match('/v1/marketplaces');
$expected = array(
'collection' => true,
'class' => 'Balanced\Marketplace',
);
$this->assertEquals($expected, $result);
$result = Resource::getRegistry()->match('/v1/marketplaces/1122');
$expected = array(
'collection' => false,
'class' => 'Balanced\Marketplace',
'ids' => array('id' => '1122'),
);
$this->assertEquals($expected, $result);
}
function testCreateCard()
{
$collection = $this->getMock(
'\RESTful\Collection',
array('create'),
array('\Balanced\Card', 'some/uri', null)
);
$collection->expects($this->once())
->method('create')
->with(array(
'street_address' => '123 Fake Street',
'city' => 'Jollywood',
'region' => '',
'postal_code' => '90210',
'name' => 'khalkhalash',
'card_number' => '4112344112344113',
'security_code' => '123',
'expiration_month' => 12,
'expiration_year' => 2013,
));
$marketplace = new Marketplace(array('cards' => $collection));
$marketplace->createCard(
'123 Fake Street',
'Jollywood',
'',
'90210',
'khalkhalash',
'4112344112344113',
'123',
12,
2013);
}
function testCreateBankAccount()
{
$collection = $this->getMock(
'\RESTful\Collection',
array('create'),
array('\Balanced\BankAccount', 'some/uri', null)
);
$collection->expects($this->once())
->method('create')
->with(array(
'name' => 'Homer Jay',
'account_number' => '112233a',
'routing_number' => '121042882',
'type' => 'savings',
'meta' => null
));
$marketplace = new Marketplace(array('bank_accounts' => $collection));
$marketplace->createBankAccount(
'Homer Jay',
'112233a',
'121042882',
'savings');
}
function testCreateAccount()
{
$collection = $this->getMock(
'\RESTful\Collection',
array('create'),
array('\Balanced\Account', 'some/uri', null)
);
$collection->expects($this->once())
->method('create')
->with(array(
'email_address' => 'role-less@example.com',
'meta' => array('test#' => 'test_d')
));
$marketplace = new Marketplace(array('accounts' => $collection));
$marketplace->createAccount(
'role-less@example.com',
array('test#' => 'test_d')
);
}
function testCreateBuyer()
{
$collection = $this->getMock(
'\RESTful\Collection',
array('create'),
array('\Balanced\Account', 'some/uri', null)
);
$collection->expects($this->once())
->method('create')
->with(array(
'email_address' => 'buyer@example.com',
'card_uri' => '/some/card/uri',
'meta' => array('test#' => 'test_d'),
'name' => 'Buy Er'
));
$marketplace = new Marketplace(array('accounts' => $collection));
$marketplace->createBuyer(
'buyer@example.com',
'/some/card/uri',
array('test#' => 'test_d'),
'Buy Er'
);
}
}
class AccountTest extends \PHPUnit_Framework_TestCase
{
function testRegistry()
{
$result = Resource::getRegistry()->match('/v1/accounts');
$expected = array(
'collection' => true,
'class' => 'Balanced\Account',
);
$this->assertEquals($expected, $result);
$result = Resource::getRegistry()->match('/v1/accounts/0099');
$expected = array(
'collection' => false,
'class' => 'Balanced\Account',
'ids' => array('id' => '0099'),
);
$this->assertEquals($expected, $result);
}
function testCredit()
{
$collection = $this->getMock(
'\RESTful\Collection',
array('create'),
array('\Balanced\Credit', 'some/uri', null)
);
$collection
->expects($this->once())
->method('create')
->with(array(
'amount' => 101,
'description' => 'something sweet',
'meta' => null,
'destination_uri' => null,
'appears_on_statement_as' => null
));
$account = new Account(array('credits' => $collection));
$account->credit(101, 'something sweet');
}
function testDebit()
{
$collection = $this->getMock(
'\RESTful\Collection',
array('create'),
array('\Balanced\Debit', 'some/uri', null)
);
$collection
->expects($this->once())
->method('create')
->with(array(
'amount' => 9911,
'description' => 'something tangy',
'appears_on_statement_as' => 'BAL*TANG',
'meta' => null,
'source_uri' => null,
'on_behalf_of_uri' => null,
));
$account = new Account(array('debits' => $collection));
$account->debit(9911, 'BAL*TANG', 'something tangy');
}
function testHold()
{
$collection = $this->getMock(
'\RESTful\Collection',
array('create'),
array('\Balanced\Hold', 'some/uri', null)
);
$collection
->expects($this->once())
->method('create')
->with(array(
'amount' => 1243,
'description' => 'something crispy',
'source_uri' => '/some/card/uri',
'meta' => array('test#' => 'test_d')
));
$account = new Account(array('holds' => $collection));
$account->hold(
1243,
'something crispy',
'/some/card/uri',
array('test#' => 'test_d')
);
}
function testAddCard()
{
$account = $this->getMock(
'\Balanced\Account',
array('save')
);
$account
->expects($this->once())
->method('save')
->with();
$account->addCard('/my/new/card/121212');
$this->assertEquals($account->card_uri, '/my/new/card/121212');
}
function testAddBankAccount()
{
$account = $this->getMock(
'\Balanced\Account',
array('save')
);
$account
->expects($this->once())
->method('save')
->with();
$account->addBankAccount('/my/new/bank_account/121212');
$this->assertEquals($account->bank_account_uri, '/my/new/bank_account/121212');
}
function testPromotToMerchant()
{
$account = $this->getMock(
'\Balanced\Account',
array('save')
);
$account
->expects($this->once())
->method('save')
->with();
$merchant = array(
'type' => 'person',
'name' => 'William James',
'tax_id' => '393-48-3992',
'street_address' => '167 West 74th Street',
'postal_code' => '10023',
'dob' => '1842-01-01',
'phone_number' => '+16505551234',
'country_code' => 'USA'
);
$account->promoteToMerchant($merchant);
$this->assertEquals($account->merchant, $merchant);
}
}
class HoldTest extends \PHPUnit_Framework_TestCase
{
function testRegistry()
{
$result = Resource::getRegistry()->match('/v1/holds');
$expected = array(
'collection' => true,
'class' => 'Balanced\Hold',
);
$this->assertEquals($expected, $result);
$result = Resource::getRegistry()->match('/v1/holds/112233');
$expected = array(
'collection' => false,
'class' => 'Balanced\Hold',
'ids' => array('id' => '112233'),
);
$this->assertEquals($expected, $result);
}
function testVoid()
{
$hold = $this->getMock(
'\Balanced\Hold',
array('save')
);
$hold
->expects($this->once())
->method('save')
->with();
$hold->void();
$this->assertTrue($hold->is_void);
}
function testCapture()
{
$collection = $this->getMock(
'\RESTful\Collection',
array('create'),
array('\Balanced\Debit', 'some/uri', null)
);
$collection
->expects($this->once())
->method('create')
->with(array(
'hold_uri' => 'some/hold/uri',
'amount' => 2211,
));
$account = new Account(array('debits' => $collection));
$hold = new Hold(array('uri' => 'some/hold/uri', 'account' => $account));
$hold->capture(2211);
}
}
class CreditTest extends \PHPUnit_Framework_TestCase
{
function testRegistry()
{
$result = Resource::getRegistry()->match('/v1/credits');
$expected = array(
'collection' => true,
'class' => 'Balanced\Credit',
);
$this->assertEquals($expected, $result);
$result = Resource::getRegistry()->match('/v1/credits/9988');
$expected = array(
'collection' => false,
'class' => 'Balanced\Credit',
'ids' => array('id' => '9988'),
);
$this->assertEquals($expected, $result);
}
}
class DebitTest extends \PHPUnit_Framework_TestCase
{
function testRegistry()
{
$result = Resource::getRegistry()->match('/v1/debits');
$expected = array(
'collection' => true,
'class' => 'Balanced\Debit',
);
$this->assertEquals($expected, $result);
$result = Resource::getRegistry()->match('/v1/debits/4545');
$expected = array(
'collection' => false,
'class' => 'Balanced\Debit',
'ids' => array('id' => '4545'),
);
$this->assertEquals($expected, $result);
}
function testRefund()
{
$collection = $this->getMock(
'\RESTful\Collection',
array('create'),
array('\Balanced\Refund', 'some/uri', null)
);
$collection
->expects($this->once())
->method('create')
->with(array(
'amount' => 5645,
'description' => null,
'meta' => array('test#' => 'test_d')
));
$debit = new Debit(array('refunds' => $collection));
$debit->refund(5645, null, array('test#' => 'test_d'));
}
}
class RefundTest extends \PHPUnit_Framework_TestCase
{
function testRegistry()
{
$result = Resource::getRegistry()->match('/v1/refunds');
$expected = array(
'collection' => true,
'class' => 'Balanced\Refund',
);
$this->assertEquals($expected, $result);
$result = Resource::getRegistry()->match('/v1/refunds/1287');
$expected = array(
'collection' => false,
'class' => 'Balanced\Refund',
'ids' => array('id' => '1287'),
);
$this->assertEquals($expected, $result);
}
}
class BankAccountTest extends \PHPUnit_Framework_TestCase
{
function testRegistry()
{
$result = Resource::getRegistry()->match('/v1/bank_accounts');
$expected = array(
'collection' => true,
'class' => 'Balanced\BankAccount',
);
$this->assertEquals($expected, $result);
$result = Resource::getRegistry()->match('/v1/bank_accounts/887766');
$expected = array(
'collection' => false,
'class' => 'Balanced\BankAccount',
'ids' => array('id' => '887766'),
);
$this->assertEquals($expected, $result);
}
function testCreditAccount()
{
$collection = $this->getMock(
'\RESTful\Collection',
array('create'),
array('\Balanced\Credit', 'some/uri', null)
);
$collection
->expects($this->once())
->method('create')
->with(array(
'amount' => 101,
'description' => 'something super sweet',
'meta' => null,
'destination_uri' => '/some/other/uri',
'appears_on_statement_as' => null
));
$account = new Account(array('credits' => $collection));
$bank_account = new BankAccount(array('uri' => '/some/other/uri', 'account' => $account));
$bank_account->credit(101, 'something super sweet');
}
function testCreditAccountless()
{
$collection = $this->getMock(
'\RESTful\Collection',
array('create'),
array('\Balanced\Credit', 'some/uri', null)
);
$collection
->expects($this->once())
->method('create')
->with(array(
'amount' => 101,
'description' => 'something super sweet',
));
$bank_account = new BankAccount(array(
'uri' => '/some/other/uri',
'account' => null,
'credits' => $collection,
));
$bank_account->credit(101, 'something super sweet');
}
}
class CardTest extends \PHPUnit_Framework_TestCase
{
function testRegistry()
{
$result = Resource::getRegistry()->match('/v1/cards');
$expected = array(
'collection' => true,
'class' => 'Balanced\Card',
);
$this->assertEquals($expected, $result);
$result = Resource::getRegistry()->match('/v1/cards/136asd6713');
$expected = array(
'collection' => false,
'class' => 'Balanced\Card',
'ids' => array('id' => '136asd6713'),
);
$this->assertEquals($expected, $result);
}
function testDebit()
{
$collection = $this->getMock(
'\RESTful\Collection',
array('create'),
array('\Balanced\Debit', 'some/uri', null)
);
$account = new Account(array('debits' => $collection));
$card = new Card(array('uri' => '/some/uri', 'account' => $account ));
$collection
->expects($this->once())
->method('create')
->with(array(
'amount' => 9911,
'description' => 'something tangy',
'appears_on_statement_as' => 'BAL*TANG',
'meta' => null,
'source_uri' => '/some/uri',
'on_behalf_of_uri' => null,
));
$card->debit(9911, 'BAL*TANG', 'something tangy');
}
/**
* @expectedException \UnexpectedValueException
*/
function testNotAssociatedDebit()
{
$card = new Card(array('uri' => '/some/uri', 'account' => null ));
$card->debit(9911, 'BAL*TANG', 'something tangy');
}
}
class MerchantTest extends \PHPUnit_Framework_TestCase
{
function testRegistry()
{
$result = Resource::getRegistry()->match('/v1/merchants');
$expected = array(
'collection' => true,
'class' => 'Balanced\Merchant',
);
$this->assertEquals($expected, $result);
$result = Resource::getRegistry()->match('/v1/merchants/136asd6713');
$expected = array(
'collection' => false,
'class' => 'Balanced\Merchant',
'ids' => array('id' => '136asd6713'),
);
$this->assertEquals($expected, $result);
}
}

View File

@@ -0,0 +1,800 @@
<?php
namespace Balanced\Test;
\Balanced\Bootstrap::init();
\RESTful\Bootstrap::init();
\Httpful\Bootstrap::init();
use Balanced\Settings;
use Balanced\APIKey;
use Balanced\Marketplace;
use Balanced\Credit;
use Balanced\Debit;
use Balanced\Refund;
use Balanced\Account;
use Balanced\Merchant;
use Balanced\BankAccount;
use Balanced\Card;
/**
* Suite test cases. These talk to an API server and so make network calls.
*
* Environment variables can be used to control client settings:
*
* <ul>
* <li>$BALANCED_URL_ROOT If set applies to \Balanced\Settings::$url_root.
* <li>$BALANCED_API_KEY If set applies to \Balanced\Settings::$api_key.
* </ul>
*
* @group suite
*/
class SuiteTest extends \PHPUnit_Framework_TestCase
{
static $key,
$marketplace,
$email_counter = 0;
static function _createBuyer($email_address = null, $card = null)
{
if ($email_address == null)
$email_address = sprintf('m+%d@poundpay.com', self::$email_counter++);
if ($card == null)
$card = self::_createCard();
return self::$marketplace->createBuyer(
$email_address,
$card->uri,
array('test#' => 'test_d'),
'Hobo Joe'
);
}
static function _createCard($account = null)
{
$card = self::$marketplace->createCard(
'123 Fake Street',
'Jollywood',
null,
'90210',
'khalkhalash',
'4112344112344113',
null,
12,
2013);
if ($account != null) {
$account->addCard($card);
$card = Card::get($card->uri);
}
return $card;
}
static function _createBankAccount($account = null)
{
$bank_account = self::$marketplace->createBankAccount(
'Homer Jay',
'112233a',
'121042882',
'checking'
);
if ($account != null) {
$account->addBankAccount($bank_account);
$bank_account = $account->bank_accounts[0];
}
return $bank_account;
}
public static function _createPersonMerchant($email_address = null, $bank_account = null)
{
if ($email_address == null)
$email_address = sprintf('m+%d@poundpay.com', self::$email_counter++);
if ($bank_account == null)
$bank_account = self::_createBankAccount();
$merchant = array(
'type' => 'person',
'name' => 'William James',
'tax_id' => '393-48-3992',
'street_address' => '167 West 74th Street',
'postal_code' => '10023',
'dob' => '1842-01-01',
'phone_number' => '+16505551234',
'country_code' => 'USA'
);
return self::$marketplace->createMerchant(
$email_address,
$merchant,
$bank_account->uri
);
}
public static function _createBusinessMerchant($email_address = null, $bank_account = null)
{
if ($email_address == null)
$email_address = sprintf('m+%d@poundpay.com', self::$email_counter++);
if ($bank_account == null)
$bank_account = self::_createBankAccount();
$merchant = array(
'type' => 'business',
'name' => 'Levain Bakery',
'tax_id' => '253912384',
'street_address' => '167 West 74th Street',
'postal_code' => '10023',
'phone_number' => '+16505551234',
'country_code' => 'USA',
'person' => array(
'name' => 'William James',
'tax_id' => '393483992',
'street_address' => '167 West 74th Street',
'postal_code' => '10023',
'dob' => '1842-01-01',
'phone_number' => '+16505551234',
'country_code' => 'USA',
),
);
return self::$marketplace->createMerchant(
$email_address,
$merchant,
$bank_account->uri
);
}
public static function setUpBeforeClass()
{
// url root
$url_root = getenv('BALANCED_URL_ROOT');
if ($url_root != '') {
Settings::$url_root = $url_root;
}
else
Settings::$url_root = 'https://api.balancedpayments.com';
// api key
$api_key = getenv('BALANCED_API_KEY');
if ($api_key != '') {
Settings::$api_key = $api_key;
}
else {
self::$key = new APIKey();
self::$key->save();
Settings::$api_key = self::$key->secret;
}
// marketplace
try {
self::$marketplace = Marketplace::mine();
}
catch(\RESTful\Exceptions\NoResultFound $e) {
self::$marketplace = new Marketplace();
self::$marketplace->save();
}
}
function testMarketplaceMine()
{
$marketplace = Marketplace::mine();
$this->assertEquals($this::$marketplace->id, $marketplace->id);
}
/**
* @expectedException \RESTful\Exceptions\HTTPError
*/
function testAnotherMarketplace()
{
$marketplace = new Marketplace();
$marketplace->save();
}
/**
* @expectedException \RESTful\Exceptions\HTTPError
*/
function testDuplicateEmailAddress()
{
self::_createBuyer('dupe@poundpay.com');
self::_createBuyer('dupe@poundpay.com');
}
function testIndexMarketplace()
{
$marketplaces = Marketplace::query()->all();
$this->assertEquals(count($marketplaces), 1);
}
function testCreateBuyer()
{
self::_createBuyer();
}
function testCreateAccountWithoutEmailAddress()
{
self::$marketplace->createAccount();
}
function testFindOrCreateAccountByEmailAddress()
{
$account1 = self::$marketplace->createAccount('foc@example.com');
$account2 = self::$marketplace->findOrCreateAccountByEmailAddress('foc@example.com');
$this->assertEquals($account2->id, $account2->id);
$account3 = self::$marketplace->findOrCreateAccountByEmailAddress('foc2@example.com');
$this->assertNotEquals($account3->id, $account1->id);
}
function testGetBuyer()
{
$buyer1 = self::_createBuyer();
$buyer2 = Account::get($buyer1->uri);
$this->assertEquals($buyer1->id, $buyer2->id);
}
function testMe()
{
$marketplace = Marketplace::mine();
$merchant = Merchant::me();
$this->assertEquals($marketplace->id, $merchant->marketplace->id);
}
function testDebitAndRefundBuyer()
{
$buyer = self::_createBuyer();
$debit = $buyer->debit(
1000,
'Softie',
'something i bought',
array('hi' => 'bye')
);
$refund = $debit->refund(100);
}
/**
* @expectedException \RESTful\Exceptions\HTTPError
*/
function testDebitZero()
{
$buyer = self::_createBuyer();
$debit = $buyer->debit(
0,
'Softie',
'something i bought'
);
}
function testMultipleRefunds()
{
$buyer = self::_createBuyer();
$debit = $buyer->debit(
1500,
'Softie',
'something tart',
array('hi' => 'bye'));
$refunds = array(
$debit->refund(100),
$debit->refund(100),
$debit->refund(100),
$debit->refund(100));
$expected_refund_ids = array_map(
function($x) {
return $x->id;
}, $refunds);
sort($expected_refund_ids);
$this->assertEquals($debit->refunds->total(), 4);
// itemization
$total = 0;
$refund_ids = array();
foreach ($debit->refunds as $refund) {
$total += $refund->amount;
array_push($refund_ids, $refund->id);
}
sort($refund_ids);
$this->assertEquals($total, 400);
$this->assertEquals($expected_refund_ids, $refund_ids);
// pagination
$total = 0;
$refund_ids = array();
foreach ($debit->refunds->paginate() as $page) {
foreach ($page->items as $refund) {
$total += $refund->amount;
array_push($refund_ids, $refund->id);
}
}
sort($refund_ids);
$this->assertEquals($total, 400);
$this->assertEquals($expected_refund_ids, $refund_ids);
}
function testDebitSource()
{
$buyer = self::_createBuyer();
$card1 = self::_createCard($buyer);
$card2 = self::_createCard($buyer);
$credit = $buyer->debit(
1000,
'Softie',
'something i bought'
);
$this->assertEquals($credit->source->id, $card2->id);
$credit = $buyer->debit(
1000,
'Softie',
'something i bought',
null,
$card1
);
$this->assertEquals($credit->source->id, $card1->id);
}
function testDebitOnBehalfOf()
{
$buyer = self::_createBuyer();
$merchant = self::$marketplace->createAccount(null);
$card1 = self::_createCard($buyer);
$debit = $buyer->debit(1000, null, null, null, null, $merchant);
$this->assertEquals($debit->amount, 1000);
// for now just test the debit succeeds.
// TODO: once the on_behalf_of actually shows up on the response, test it.
}
/**
* @expectedException \InvalidArgumentException
*/
function testDebitOnBehalfOfFailsForBuyer()
{
$buyer = self::_createBuyer();
$card1 = self::_createCard($buyer);
$debit = $buyer->debit(1000, null, null, null, null, $buyer);
}
function testCreateAndVoidHold()
{
$buyer = self::_createBuyer();
$hold = $buyer->hold(1000);
$this->assertEquals($hold->is_void, false);
$hold->void();
$this->assertEquals($hold->is_void, true);
}
function testCreateAndCaptureHold()
{
$buyer = self::_createBuyer();
$hold = $buyer->hold(1000);
$debit = $hold->capture(909);
$this->assertEquals($debit->account->id, $buyer->id);
$this->assertEquals($debit->hold->id, $hold->id);
$this->assertEquals($hold->debit->id, $debit->id);
}
function testCreatePersonMerchant()
{
$merchant = self::_createPersonMerchant();
}
function testCreateBusinessMerchant()
{
$merchant = self::_createBusinessMerchant();
}
/**
* @expectedException \RESTful\Exceptions\HTTPError
*/
function testCreditRequiresNonZeroAmount()
{
$buyer = self::_createBuyer();
$buyer->debit(
1000,
'Softie',
'something i bought'
);
$merchant = self::_createBusinessMerchant();
$merchant->credit(0);
}
/**
* @expectedException \RESTful\Exceptions\HTTPError
*/
function testCreditMoreThanEscrowBalanceFails()
{
$buyer = self::_createBuyer();
$buyer->credit(
1000,
'something i bought',
null,
null,
'Softie'
);
$merchant = self::_createBusinessMerchant();
$merchant->credit(self::$marketplace->in_escrow + 1);
}
function testCreditDestiation()
{
$buyer = self::_createBuyer();
$buyer->debit(3000); # NOTE: build up escrow balance to credit
$merchant = self::_createPersonMerchant();
$bank_account1 = self::_createBankAccount($merchant);
$bank_account2 = self::_createBankAccount($merchant);
$credit = $merchant->credit(
1000,
'something i sold',
null,
null,
'Softie'
);
$this->assertEquals($credit->destination->id, $bank_account2->id);
$credit = $merchant->credit(
1000,
'something i sold',
null,
$bank_account1,
'Softie'
);
$this->assertEquals($credit->destination->id, $bank_account1->id);
}
function testAssociateCard()
{
$merchant = self::_createPersonMerchant();
$card = self::_createCard();
$merchant->addCard($card->uri);
}
function testAssociateBankAccount()
{
$merchant = self::_createPersonMerchant();
$bank_account = self::_createBankAccount();
$merchant->addBankAccount($bank_account->uri);
}
function testCardMasking()
{
$card = self::$marketplace->createCard(
'123 Fake Street',
'Jollywood',
null,
'90210',
'khalkhalash',
'4112344112344113',
'123',
12,
2013);
$this->assertEquals($card->last_four, '4113');
$this->assertFalse(property_exists($card, 'number'));
}
function testBankAccountMasking()
{
$bank_account = self::$marketplace->createBankAccount(
'Homer Jay',
'112233a',
'121042882',
'checking'
);
$this->assertEquals($bank_account->last_four, '233a');
$this->assertEquals($bank_account->account_number, 'xxx233a');
}
function testFilteringAndSorting()
{
$buyer = self::_createBuyer();
$debit1 = $buyer->debit(1122, null, null, array('tag' => '1'));
$debit2 = $buyer->debit(3322, null, null, array('tag' => '1'));
$debit3 = $buyer->debit(2211, null, null, array('tag' => '2'));
$getId = function($o) {
return $o->id;
};
$debits = (
self::$marketplace->debits->query()
->filter(Debit::$f->meta->tag->eq('1'))
->sort(Debit::$f->created_at->asc())
->all());
$debit_ids = array_map($getId, $debits);
$this->assertEquals($debit_ids, array($debit1->id, $debit2->id));
$debits = (
self::$marketplace->debits->query()
->filter(Debit::$f->meta->tag->eq('2'))
->all());
$debit_ids = array_map($getId, $debits);
$this->assertEquals($debit_ids, array($debit3->id));
$debits = (
self::$marketplace->debits->query()
->filter(Debit::$f->meta->contains('tag'))
->sort(Debit::$f->created_at->asc())
->all());
$debit_ids = array_map($getId, $debits);
$this->assertEquals($debit_ids, array($debit1->id, $debit2->id, $debit3->id));
$debits = (
self::$marketplace->debits->query()
->filter(Debit::$f->meta->contains('tag'))
->sort(Debit::$f->amount->desc())
->all());
$debit_ids = array_map($getId, $debits);
$this->assertEquals($debit_ids, array($debit2->id, $debit3->id, $debit1->id));
}
function testMerchantIdentityFailure()
{
// NOTE: postal_code == '99999' && region == 'EX' triggers identity failure
$identity = array(
'type' => 'business',
'name' => 'Levain Bakery',
'tax_id' => '253912384',
'street_address' => '167 West 74th Street',
'postal_code' => '99999',
'region' => 'EX',
'phone_number' => '+16505551234',
'country_code' => 'USA',
'person' => array(
'name' => 'William James',
'tax_id' => '393483992',
'street_address' => '167 West 74th Street',
'postal_code' => '99999',
'region' => 'EX',
'dob' => '1842-01-01',
'phone_number' => '+16505551234',
'country_code' => 'USA',
),
);
try {
self::$marketplace->createMerchant(
sprintf('m+%d@poundpay.com', self::$email_counter++),
$identity);
}
catch(\RESTful\Exceptions\HTTPError $e) {
$this->assertEquals($e->response->code, 300);
$expected = sprintf('https://www.balancedpayments.com/marketplaces/%s/kyc', self::$marketplace->id);
$this->assertEquals($e->redirect_uri, $expected);
$this->assertEquals($e->response->headers['Location'], $expected);
return;
}
$this->fail('Expected exception HTTPError not raised.');
}
function testInternationalCard()
{
$payload = array(
'card_number' => '4111111111111111',
'city' => '\xe9\x83\xbd\xe7\x95\x99\xe5\xb8\x82',
'country_code' => 'JPN',
'expiration_month' => 12,
'expiration_year' => 2014,
'name' => 'Johnny Fresh',
'postal_code' => '4020054',
'street_address' => '\xe7\x94\xb0\xe5\x8e\x9f\xef\xbc\x93\xe3\x83\xbc\xef\xbc\x98\xe3\x83\xbc\xef\xbc\x91'
);
$card = self::$marketplace->cards->create($payload);
$this->assertEquals($card->street_address, $payload['street_address']);
}
/**
* @expectedException \RESTful\Exceptions\NoResultFound
*/
function testAccountWithEmailAddressNotFound()
{
self::$marketplace->accounts->query()
->filter(Account::$f->email_address->eq('unlikely@address.com'))
->one();
}
function testDebitACard()
{
$buyer = self::_createBuyer();
$card = self::_createCard($buyer);
$debit = $card->debit(
1000,
'Softie',
'something i bought',
array('hi' => 'bye'));
$this->assertEquals($debit->source->uri, $card->uri);
}
/**
* @expectedException \UnexpectedValueException
*/
function testDebitAnUnassociatedCard()
{
$card = self::_createCard();
$card->debit(1000, 'Softie');
}
function testCreditABankAccount()
{
$buyer = self::_createBuyer();
$buyer->debit(101); # NOTE: build up escrow balance to credit
$merchant = self::_createPersonMerchant();
$bank_account = self::_createBankAccount($merchant);
$credit = $bank_account->credit(55, 'something sour');
$this->assertEquals($credit->destination->uri, $bank_account->uri);
}
function testQuery()
{
$buyer = self::_createBuyer();
$tag = '123123123123';
$debit1 = $buyer->debit(1122, null, null, array('tag' => $tag));
$debit2 = $buyer->debit(3322, null, null, array('tag' => $tag));
$debit3 = $buyer->debit(2211, null, null, array('tag' => $tag));
$expected_debit_ids = array($debit1->id, $debit2->id, $debit3->id);
$query = (
self::$marketplace->debits->query()
->filter(Debit::$f->meta->tag->eq($tag))
->sort(Debit::$f->created_at->asc())
->limit(1));
$this->assertEquals($query->total(), 3);
$debit_ids = array();
foreach ($query as $debits) {
array_push($debit_ids, $debits->id);
}
$this->assertEquals($debit_ids, $expected_debit_ids);
$debit_ids = array($query[0]->id, $query[1]->id, $query[2]->id);
$this->assertEquals($debit_ids, $expected_debit_ids);
}
function testBuyerPromoteToMerchant()
{
$merchant = array(
'type' => 'person',
'name' => 'William James',
'tax_id' => '393-48-3992',
'street_address' => '167 West 74th Street',
'postal_code' => '10023',
'dob' => '1842-01-01',
'phone_number' => '+16505551234',
'country_code' => 'USA'
);
$buyer = self::_createBuyer();
$buyer->promoteToMerchant($merchant);
}
function testCreditAccountlessBankAccount()
{
$buyer = self::_createBuyer();
$buyer->debit(101); # NOTE: build up escrow balance to credit
$bank_account = self::_createBankAccount();
$credit = $bank_account->credit(55, 'something sour');
$this->assertEquals($credit->bank_account->id, $bank_account->id);
$bank_account = $bank_account->get($bank_account->id);
$this->assertEquals($bank_account->credits->total(), 1);
}
function testCreditUnstoredBankAccount()
{
$buyer = self::_createBuyer();
$buyer->debit(101); # NOTE: build up escrow balance to credit
$credit = Credit::bankAccount(
55,
array(
'name' => 'Homer Jay',
'account_number' => '112233a',
'routing_number' => '121042882',
'type' => 'checking',
),
'something sour');
$this->assertFalse(property_exists($credit->bank_account, 'uri'));
$this->assertFalse(property_exists($credit->bank_account, 'id'));
$this->assertEquals($credit->bank_account->name, 'Homer Jay');
$this->assertEquals($credit->bank_account->account_number, 'xxx233a');
$this->assertEquals($credit->bank_account->type, 'checking');
}
function testDeleteBankAccount()
{
$buyer = self::_createBuyer();
$buyer->debit(101); # NOTE: build up escrow balance to credit
$bank_account = self::_createBankAccount();
$credit = $bank_account->credit(55, 'something sour');
$this->assertTrue(property_exists($credit->bank_account, 'uri'));
$this->assertTrue(property_exists($credit->bank_account, 'id'));
$bank_account = BankAccount::get($bank_account->id);
$bank_account->delete();
$credit = Credit::get($credit->uri);
$this->assertFalse(property_exists($credit->bank_account, 'uri'));
$this->assertFalse(property_exists($credit->bank_account, 'id'));
}
function testGetBankAccounById()
{
$bank_account = self::_createBankAccount();
$bank_account_2 = BankAccount::get($bank_account->id);
$this->assertEquals($bank_account_2->id, $bank_account->id);
}
/**
* @expectedException \Balanced\Errors\InsufficientFunds
*/
function testInsufficientFunds()
{
$marketplace = Marketplace::get(self::$marketplace->uri);
$amount = $marketplace->in_escrow + 100;
$credit = Credit::bankAccount(
$amount,
array(
'name' => 'Homer Jay',
'account_number' => '112233a',
'routing_number' => '121042882',
'type' => 'checking',
),
'something sour');
}
function testCreateCallback() {
$callback = self::$marketplace->createCallback(
'http://example.com/php'
);
$this->assertEquals($callback->url, 'http://example.com/php');
}
/**
* @expectedException \Balanced\Errors\BankAccountVerificationFailure
*/
function testBankAccountVerificationFailure() {
$bank_account = self::_createBankAccount();
$buyer = self::_createBuyer();
$buyer->addBankAccount($bank_account);
$verification = $bank_account->verify();
$verification->confirm(1, 2);
}
/**
* @expectedException \Balanced\Errors\BankAccountVerificationFailure
*/
function testBankAccountVerificationDuplicate() {
$bank_account = self::_createBankAccount();
$buyer = self::_createBuyer();
$buyer->addBankAccount($bank_account);
$bank_account->verify();
$bank_account->verify();
}
function testBankAccountVerificationSuccess() {
$bank_account = self::_createBankAccount();
$buyer = self::_createBuyer();
$buyer->addBankAccount($bank_account);
$verification = $bank_account->verify();
$verification->confirm(1, 1);
// this will fail if the bank account is not verified
$debit = $buyer->debit(
1000,
'Softie',
'something i bought',
array('hi' => 'bye'),
$bank_account
);
$this->assertTrue(strpos($debit->source->uri, 'bank_account') > 0);
}
function testEvents() {
$prev_num_events = Marketplace::mine()->events->total();
$account = self::_createBuyer();
$account->debit(123);
$cur_num_events = Marketplace::mine()->events->total();
$count = 0;
while ($cur_num_events == $prev_num_events && $count < 10) {
printf("waiting for events - %d, %d == %d\n", $count + 1, $cur_num_events, $prev_num_events);
sleep(2); // 2 seconds
$cur_num_events = Marketplace::mine()->events->total();
$count += 1;
}
$this->assertTrue($cur_num_events > $prev_num_events);
}
}

View File

@@ -0,0 +1,8 @@
<phpunit>
<testsuite name="Balanced">
<directory>.</directory>
</testsuite>
<logging>
<log type="coverage-text" target="php://stdout" showUncoveredFiles="false"/>
</logging>
</phpunit>

View File

@@ -1,769 +0,0 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE supplementalData SYSTEM "../../common/dtd/ldmlSupplemental.dtd">
<!--
Copyright © 1991-2013 Unicode, Inc.
CLDR data files are interpreted according to the LDML specification (http://unicode.org/reports/tr35/)
For terms of use, see http://www.unicode.org/copyright.html
-->
<supplementalData>
<version number="$Revision$"/>
<windowsZones>
<mapTimezones otherVersion="7e00402" typeVersion="2016i">
<!-- (UTC-12:00) International Date Line West -->
<mapZone other="Dateline Standard Time" territory="001" type="Etc/GMT+12"/>
<mapZone other="Dateline Standard Time" territory="ZZ" type="Etc/GMT+12"/>
<!-- (UTC-11:00) Coordinated Universal Time-11 -->
<mapZone other="UTC-11" territory="001" type="Etc/GMT+11"/>
<mapZone other="UTC-11" territory="AS" type="Pacific/Pago_Pago"/>
<mapZone other="UTC-11" territory="NU" type="Pacific/Niue"/>
<mapZone other="UTC-11" territory="UM" type="Pacific/Midway"/>
<mapZone other="UTC-11" territory="ZZ" type="Etc/GMT+11"/>
<!-- (UTC-10:00) Aleutian Islands -->
<mapZone other="Aleutian Standard Time" territory="001" type="America/Adak"/>
<mapZone other="Aleutian Standard Time" territory="US" type="America/Adak"/>
<!-- (UTC-10:00) Hawaii -->
<mapZone other="Hawaiian Standard Time" territory="001" type="Pacific/Honolulu"/>
<mapZone other="Hawaiian Standard Time" territory="CK" type="Pacific/Rarotonga"/>
<mapZone other="Hawaiian Standard Time" territory="PF" type="Pacific/Tahiti"/>
<mapZone other="Hawaiian Standard Time" territory="UM" type="Pacific/Johnston"/>
<mapZone other="Hawaiian Standard Time" territory="US" type="Pacific/Honolulu"/>
<mapZone other="Hawaiian Standard Time" territory="ZZ" type="Etc/GMT+10"/>
<!-- (UTC-09:30) Marquesas Islands -->
<mapZone other="Marquesas Standard Time" territory="001" type="Pacific/Marquesas"/>
<mapZone other="Marquesas Standard Time" territory="PF" type="Pacific/Marquesas"/>
<!-- (UTC-09:00) Alaska -->
<mapZone other="Alaskan Standard Time" territory="001" type="America/Anchorage"/>
<mapZone other="Alaskan Standard Time" territory="US" type="America/Anchorage America/Juneau America/Metlakatla America/Nome America/Sitka America/Yakutat"/>
<!-- (UTC-09:00) Coordinated Universal Time-09 -->
<mapZone other="UTC-09" territory="001" type="Etc/GMT+9"/>
<mapZone other="UTC-09" territory="PF" type="Pacific/Gambier"/>
<mapZone other="UTC-09" territory="ZZ" type="Etc/GMT+9"/>
<!-- (UTC-08:00) Baja California -->
<mapZone other="Pacific Standard Time (Mexico)" territory="001" type="America/Tijuana"/>
<mapZone other="Pacific Standard Time (Mexico)" territory="MX" type="America/Tijuana America/Santa_Isabel"/>
<!-- (UTC-08:00) Coordinated Universal Time-08 -->
<mapZone other="UTC-08" territory="001" type="Etc/GMT+8"/>
<mapZone other="UTC-08" territory="PN" type="Pacific/Pitcairn"/>
<mapZone other="UTC-08" territory="ZZ" type="Etc/GMT+8"/>
<!-- (UTC-08:00) Pacific Time (US & Canada) -->
<mapZone other="Pacific Standard Time" territory="001" type="America/Los_Angeles"/>
<mapZone other="Pacific Standard Time" territory="CA" type="America/Vancouver America/Dawson America/Whitehorse"/>
<mapZone other="Pacific Standard Time" territory="US" type="America/Los_Angeles"/>
<mapZone other="Pacific Standard Time" territory="ZZ" type="PST8PDT"/>
<!-- (UTC-07:00) Arizona -->
<mapZone other="US Mountain Standard Time" territory="001" type="America/Phoenix"/>
<mapZone other="US Mountain Standard Time" territory="CA" type="America/Dawson_Creek America/Creston America/Fort_Nelson"/>
<mapZone other="US Mountain Standard Time" territory="MX" type="America/Hermosillo"/>
<mapZone other="US Mountain Standard Time" territory="US" type="America/Phoenix"/>
<mapZone other="US Mountain Standard Time" territory="ZZ" type="Etc/GMT+7"/>
<!-- (UTC-07:00) Chihuahua, La Paz, Mazatlan -->
<mapZone other="Mountain Standard Time (Mexico)" territory="001" type="America/Chihuahua"/>
<mapZone other="Mountain Standard Time (Mexico)" territory="MX" type="America/Chihuahua America/Mazatlan"/>
<!-- (UTC-07:00) Mountain Time (US & Canada) -->
<mapZone other="Mountain Standard Time" territory="001" type="America/Denver"/>
<mapZone other="Mountain Standard Time" territory="CA" type="America/Edmonton America/Cambridge_Bay America/Inuvik America/Yellowknife"/>
<mapZone other="Mountain Standard Time" territory="MX" type="America/Ojinaga"/>
<mapZone other="Mountain Standard Time" territory="US" type="America/Denver America/Boise"/>
<mapZone other="Mountain Standard Time" territory="ZZ" type="MST7MDT"/>
<!-- (UTC-06:00) Central America -->
<mapZone other="Central America Standard Time" territory="001" type="America/Guatemala"/>
<mapZone other="Central America Standard Time" territory="BZ" type="America/Belize"/>
<mapZone other="Central America Standard Time" territory="CR" type="America/Costa_Rica"/>
<mapZone other="Central America Standard Time" territory="EC" type="Pacific/Galapagos"/>
<mapZone other="Central America Standard Time" territory="GT" type="America/Guatemala"/>
<mapZone other="Central America Standard Time" territory="HN" type="America/Tegucigalpa"/>
<mapZone other="Central America Standard Time" territory="NI" type="America/Managua"/>
<mapZone other="Central America Standard Time" territory="SV" type="America/El_Salvador"/>
<mapZone other="Central America Standard Time" territory="ZZ" type="Etc/GMT+6"/>
<!-- (UTC-06:00) Central Time (US & Canada) -->
<mapZone other="Central Standard Time" territory="001" type="America/Chicago"/>
<mapZone other="Central Standard Time" territory="CA" type="America/Winnipeg America/Rainy_River America/Rankin_Inlet America/Resolute"/>
<mapZone other="Central Standard Time" territory="MX" type="America/Matamoros"/>
<mapZone other="Central Standard Time" territory="US" type="America/Chicago America/Indiana/Knox America/Indiana/Tell_City America/Menominee America/North_Dakota/Beulah America/North_Dakota/Center America/North_Dakota/New_Salem"/>
<mapZone other="Central Standard Time" territory="ZZ" type="CST6CDT"/>
<!-- (UTC-06:00) Easter Island -->
<mapZone other="Easter Island Standard Time" territory="001" type="Pacific/Easter"/>
<mapZone other="Easter Island Standard Time" territory="CL" type="Pacific/Easter"/>
<!-- (UTC-06:00) Guadalajara, Mexico City, Monterrey -->
<mapZone other="Central Standard Time (Mexico)" territory="001" type="America/Mexico_City"/>
<mapZone other="Central Standard Time (Mexico)" territory="MX" type="America/Mexico_City America/Bahia_Banderas America/Merida America/Monterrey"/>
<!-- (UTC-06:00) Saskatchewan -->
<mapZone other="Canada Central Standard Time" territory="001" type="America/Regina"/>
<mapZone other="Canada Central Standard Time" territory="CA" type="America/Regina America/Swift_Current"/>
<!-- (UTC-05:00) Bogota, Lima, Quito, Rio Branco -->
<mapZone other="SA Pacific Standard Time" territory="001" type="America/Bogota"/>
<mapZone other="SA Pacific Standard Time" territory="BR" type="America/Rio_Branco America/Eirunepe"/>
<mapZone other="SA Pacific Standard Time" territory="CA" type="America/Coral_Harbour"/>
<mapZone other="SA Pacific Standard Time" territory="CO" type="America/Bogota"/>
<mapZone other="SA Pacific Standard Time" territory="EC" type="America/Guayaquil"/>
<mapZone other="SA Pacific Standard Time" territory="JM" type="America/Jamaica"/>
<mapZone other="SA Pacific Standard Time" territory="KY" type="America/Cayman"/>
<mapZone other="SA Pacific Standard Time" territory="PA" type="America/Panama"/>
<mapZone other="SA Pacific Standard Time" territory="PE" type="America/Lima"/>
<mapZone other="SA Pacific Standard Time" territory="ZZ" type="Etc/GMT+5"/>
<!-- (UTC-05:00) Chetumal -->
<mapZone other="Eastern Standard Time (Mexico)" territory="001" type="America/Cancun"/>
<mapZone other="Eastern Standard Time (Mexico)" territory="MX" type="America/Cancun"/>
<!-- (UTC-05:00) Eastern Time (US & Canada) -->
<mapZone other="Eastern Standard Time" territory="001" type="America/New_York"/>
<mapZone other="Eastern Standard Time" territory="BS" type="America/Nassau"/>
<mapZone other="Eastern Standard Time" territory="CA" type="America/Toronto America/Iqaluit America/Montreal America/Nipigon America/Pangnirtung America/Thunder_Bay"/>
<mapZone other="Eastern Standard Time" territory="US" type="America/New_York America/Detroit America/Indiana/Petersburg America/Indiana/Vincennes America/Indiana/Winamac America/Kentucky/Monticello America/Louisville"/>
<mapZone other="Eastern Standard Time" territory="ZZ" type="EST5EDT"/>
<!-- (UTC-05:00) Haiti -->
<mapZone other="Haiti Standard Time" territory="001" type="America/Port-au-Prince"/>
<mapZone other="Haiti Standard Time" territory="HT" type="America/Port-au-Prince"/>
<!-- (UTC-05:00) Havana -->
<mapZone other="Cuba Standard Time" territory="001" type="America/Havana"/>
<mapZone other="Cuba Standard Time" territory="CU" type="America/Havana"/>
<!-- (UTC-05:00) Indiana (East) -->
<mapZone other="US Eastern Standard Time" territory="001" type="America/Indianapolis"/>
<mapZone other="US Eastern Standard Time" territory="US" type="America/Indianapolis America/Indiana/Marengo America/Indiana/Vevay"/>
<!-- (UTC-04:00) Asuncion -->
<mapZone other="Paraguay Standard Time" territory="001" type="America/Asuncion"/>
<mapZone other="Paraguay Standard Time" territory="PY" type="America/Asuncion"/>
<!-- (UTC-04:00) Atlantic Time (Canada) -->
<mapZone other="Atlantic Standard Time" territory="001" type="America/Halifax"/>
<mapZone other="Atlantic Standard Time" territory="BM" type="Atlantic/Bermuda"/>
<mapZone other="Atlantic Standard Time" territory="CA" type="America/Halifax America/Glace_Bay America/Goose_Bay America/Moncton"/>
<mapZone other="Atlantic Standard Time" territory="GL" type="America/Thule"/>
<!-- (UTC-04:00) Caracas -->
<mapZone other="Venezuela Standard Time" territory="001" type="America/Caracas"/>
<mapZone other="Venezuela Standard Time" territory="VE" type="America/Caracas"/>
<!-- (UTC-04:00) Cuiaba -->
<mapZone other="Central Brazilian Standard Time" territory="001" type="America/Cuiaba"/>
<mapZone other="Central Brazilian Standard Time" territory="BR" type="America/Cuiaba America/Campo_Grande"/>
<!-- (UTC-04:00) Georgetown, La Paz, Manaus, San Juan -->
<mapZone other="SA Western Standard Time" territory="001" type="America/La_Paz"/>
<mapZone other="SA Western Standard Time" territory="AG" type="America/Antigua"/>
<mapZone other="SA Western Standard Time" territory="AI" type="America/Anguilla"/>
<mapZone other="SA Western Standard Time" territory="AW" type="America/Aruba"/>
<mapZone other="SA Western Standard Time" territory="BB" type="America/Barbados"/>
<mapZone other="SA Western Standard Time" territory="BL" type="America/St_Barthelemy"/>
<mapZone other="SA Western Standard Time" territory="BO" type="America/La_Paz"/>
<mapZone other="SA Western Standard Time" territory="BQ" type="America/Kralendijk"/>
<mapZone other="SA Western Standard Time" territory="BR" type="America/Manaus America/Boa_Vista America/Porto_Velho"/>
<mapZone other="SA Western Standard Time" territory="CA" type="America/Blanc-Sablon"/>
<mapZone other="SA Western Standard Time" territory="CW" type="America/Curacao"/>
<mapZone other="SA Western Standard Time" territory="DM" type="America/Dominica"/>
<mapZone other="SA Western Standard Time" territory="DO" type="America/Santo_Domingo"/>
<mapZone other="SA Western Standard Time" territory="GD" type="America/Grenada"/>
<mapZone other="SA Western Standard Time" territory="GP" type="America/Guadeloupe"/>
<mapZone other="SA Western Standard Time" territory="GY" type="America/Guyana"/>
<mapZone other="SA Western Standard Time" territory="KN" type="America/St_Kitts"/>
<mapZone other="SA Western Standard Time" territory="LC" type="America/St_Lucia"/>
<mapZone other="SA Western Standard Time" territory="MF" type="America/Marigot"/>
<mapZone other="SA Western Standard Time" territory="MQ" type="America/Martinique"/>
<mapZone other="SA Western Standard Time" territory="MS" type="America/Montserrat"/>
<mapZone other="SA Western Standard Time" territory="PR" type="America/Puerto_Rico"/>
<mapZone other="SA Western Standard Time" territory="SX" type="America/Lower_Princes"/>
<mapZone other="SA Western Standard Time" territory="TT" type="America/Port_of_Spain"/>
<mapZone other="SA Western Standard Time" territory="VC" type="America/St_Vincent"/>
<mapZone other="SA Western Standard Time" territory="VG" type="America/Tortola"/>
<mapZone other="SA Western Standard Time" territory="VI" type="America/St_Thomas"/>
<mapZone other="SA Western Standard Time" territory="ZZ" type="Etc/GMT+4"/>
<!-- (UTC-04:00) Santiago -->
<mapZone other="Pacific SA Standard Time" territory="001" type="America/Santiago"/>
<mapZone other="Pacific SA Standard Time" territory="AQ" type="Antarctica/Palmer"/>
<mapZone other="Pacific SA Standard Time" territory="CL" type="America/Santiago"/>
<!-- (UTC-04:00) Turks and Caicos -->
<mapZone other="Turks And Caicos Standard Time" territory="001" type="America/Grand_Turk"/>
<mapZone other="Turks And Caicos Standard Time" territory="TC" type="America/Grand_Turk"/>
<!-- (UTC-03:30) Newfoundland -->
<mapZone other="Newfoundland Standard Time" territory="001" type="America/St_Johns"/>
<mapZone other="Newfoundland Standard Time" territory="CA" type="America/St_Johns"/>
<!-- (UTC-03:00) Araguaina -->
<mapZone other="Tocantins Standard Time" territory="001" type="America/Araguaina"/>
<mapZone other="Tocantins Standard Time" territory="BR" type="America/Araguaina"/>
<!-- (UTC-03:00) Brasilia -->
<mapZone other="E. South America Standard Time" territory="001" type="America/Sao_Paulo"/>
<mapZone other="E. South America Standard Time" territory="BR" type="America/Sao_Paulo"/>
<!-- (UTC-03:00) Cayenne, Fortaleza -->
<mapZone other="SA Eastern Standard Time" territory="001" type="America/Cayenne"/>
<mapZone other="SA Eastern Standard Time" territory="AQ" type="Antarctica/Rothera"/>
<mapZone other="SA Eastern Standard Time" territory="BR" type="America/Fortaleza America/Belem America/Maceio America/Recife America/Santarem"/>
<mapZone other="SA Eastern Standard Time" territory="FK" type="Atlantic/Stanley"/>
<mapZone other="SA Eastern Standard Time" territory="GF" type="America/Cayenne"/>
<mapZone other="SA Eastern Standard Time" territory="SR" type="America/Paramaribo"/>
<mapZone other="SA Eastern Standard Time" territory="ZZ" type="Etc/GMT+3"/>
<!-- (UTC-03:00) City of Buenos Aires -->
<mapZone other="Argentina Standard Time" territory="001" type="America/Buenos_Aires"/>
<mapZone other="Argentina Standard Time" territory="AR" type="America/Buenos_Aires America/Argentina/La_Rioja America/Argentina/Rio_Gallegos America/Argentina/Salta America/Argentina/San_Juan America/Argentina/San_Luis America/Argentina/Tucuman America/Argentina/Ushuaia America/Catamarca America/Cordoba America/Jujuy America/Mendoza"/>
<!-- (UTC-03:00) Greenland -->
<mapZone other="Greenland Standard Time" territory="001" type="America/Godthab"/>
<mapZone other="Greenland Standard Time" territory="GL" type="America/Godthab"/>
<!-- (UTC-03:00) Montevideo -->
<mapZone other="Montevideo Standard Time" territory="001" type="America/Montevideo"/>
<mapZone other="Montevideo Standard Time" territory="UY" type="America/Montevideo"/>
<!-- (UTC-03:00) Saint Pierre and Miquelon -->
<mapZone other="Saint Pierre Standard Time" territory="001" type="America/Miquelon"/>
<mapZone other="Saint Pierre Standard Time" territory="PM" type="America/Miquelon"/>
<!-- (UTC-03:00) Salvador -->
<mapZone other="Bahia Standard Time" territory="001" type="America/Bahia"/>
<mapZone other="Bahia Standard Time" territory="BR" type="America/Bahia"/>
<!-- (UTC-02:00) Coordinated Universal Time-02 -->
<mapZone other="UTC-02" territory="001" type="Etc/GMT+2"/>
<mapZone other="UTC-02" territory="BR" type="America/Noronha"/>
<mapZone other="UTC-02" territory="GS" type="Atlantic/South_Georgia"/>
<mapZone other="UTC-02" territory="ZZ" type="Etc/GMT+2"/>
<!-- (UTC-01:00) Azores -->
<mapZone other="Azores Standard Time" territory="001" type="Atlantic/Azores"/>
<mapZone other="Azores Standard Time" territory="GL" type="America/Scoresbysund"/>
<mapZone other="Azores Standard Time" territory="PT" type="Atlantic/Azores"/>
<!-- (UTC-01:00) Cabo Verde Is. -->
<mapZone other="Cape Verde Standard Time" territory="001" type="Atlantic/Cape_Verde"/>
<mapZone other="Cape Verde Standard Time" territory="CV" type="Atlantic/Cape_Verde"/>
<mapZone other="Cape Verde Standard Time" territory="ZZ" type="Etc/GMT+1"/>
<!-- (UTC) Coordinated Universal Time -->
<mapZone other="UTC" territory="001" type="Etc/GMT"/>
<mapZone other="UTC" territory="GL" type="America/Danmarkshavn"/>
<mapZone other="UTC" territory="ZZ" type="Etc/GMT"/>
<!-- (UTC+00:00) Casablanca -->
<mapZone other="Morocco Standard Time" territory="001" type="Africa/Casablanca"/>
<mapZone other="Morocco Standard Time" territory="EH" type="Africa/El_Aaiun"/>
<mapZone other="Morocco Standard Time" territory="MA" type="Africa/Casablanca"/>
<!-- (UTC+00:00) Dublin, Edinburgh, Lisbon, London -->
<mapZone other="GMT Standard Time" territory="001" type="Europe/London"/>
<mapZone other="GMT Standard Time" territory="ES" type="Atlantic/Canary"/>
<mapZone other="GMT Standard Time" territory="FO" type="Atlantic/Faeroe"/>
<mapZone other="GMT Standard Time" territory="GB" type="Europe/London"/>
<mapZone other="GMT Standard Time" territory="GG" type="Europe/Guernsey"/>
<mapZone other="GMT Standard Time" territory="IE" type="Europe/Dublin"/>
<mapZone other="GMT Standard Time" territory="IM" type="Europe/Isle_of_Man"/>
<mapZone other="GMT Standard Time" territory="JE" type="Europe/Jersey"/>
<mapZone other="GMT Standard Time" territory="PT" type="Europe/Lisbon Atlantic/Madeira"/>
<!-- (UTC+00:00) Monrovia, Reykjavik -->
<mapZone other="Greenwich Standard Time" territory="001" type="Atlantic/Reykjavik"/>
<mapZone other="Greenwich Standard Time" territory="BF" type="Africa/Ouagadougou"/>
<mapZone other="Greenwich Standard Time" territory="CI" type="Africa/Abidjan"/>
<mapZone other="Greenwich Standard Time" territory="GH" type="Africa/Accra"/>
<mapZone other="Greenwich Standard Time" territory="GM" type="Africa/Banjul"/>
<mapZone other="Greenwich Standard Time" territory="GN" type="Africa/Conakry"/>
<mapZone other="Greenwich Standard Time" territory="GW" type="Africa/Bissau"/>
<mapZone other="Greenwich Standard Time" territory="IS" type="Atlantic/Reykjavik"/>
<mapZone other="Greenwich Standard Time" territory="LR" type="Africa/Monrovia"/>
<mapZone other="Greenwich Standard Time" territory="ML" type="Africa/Bamako"/>
<mapZone other="Greenwich Standard Time" territory="MR" type="Africa/Nouakchott"/>
<mapZone other="Greenwich Standard Time" territory="SH" type="Atlantic/St_Helena"/>
<mapZone other="Greenwich Standard Time" territory="SL" type="Africa/Freetown"/>
<mapZone other="Greenwich Standard Time" territory="SN" type="Africa/Dakar"/>
<mapZone other="Greenwich Standard Time" territory="ST" type="Africa/Sao_Tome"/>
<mapZone other="Greenwich Standard Time" territory="TG" type="Africa/Lome"/>
<!-- (UTC+01:00) Amsterdam, Berlin, Bern, Rome, Stockholm, Vienna -->
<mapZone other="W. Europe Standard Time" territory="001" type="Europe/Berlin"/>
<mapZone other="W. Europe Standard Time" territory="AD" type="Europe/Andorra"/>
<mapZone other="W. Europe Standard Time" territory="AT" type="Europe/Vienna"/>
<mapZone other="W. Europe Standard Time" territory="CH" type="Europe/Zurich"/>
<mapZone other="W. Europe Standard Time" territory="DE" type="Europe/Berlin Europe/Busingen"/>
<mapZone other="W. Europe Standard Time" territory="GI" type="Europe/Gibraltar"/>
<mapZone other="W. Europe Standard Time" territory="IT" type="Europe/Rome"/>
<mapZone other="W. Europe Standard Time" territory="LI" type="Europe/Vaduz"/>
<mapZone other="W. Europe Standard Time" territory="LU" type="Europe/Luxembourg"/>
<mapZone other="W. Europe Standard Time" territory="MC" type="Europe/Monaco"/>
<mapZone other="W. Europe Standard Time" territory="MT" type="Europe/Malta"/>
<mapZone other="W. Europe Standard Time" territory="NL" type="Europe/Amsterdam"/>
<mapZone other="W. Europe Standard Time" territory="NO" type="Europe/Oslo"/>
<mapZone other="W. Europe Standard Time" territory="SE" type="Europe/Stockholm"/>
<mapZone other="W. Europe Standard Time" territory="SJ" type="Arctic/Longyearbyen"/>
<mapZone other="W. Europe Standard Time" territory="SM" type="Europe/San_Marino"/>
<mapZone other="W. Europe Standard Time" territory="VA" type="Europe/Vatican"/>
<!-- (UTC+01:00) Belgrade, Bratislava, Budapest, Ljubljana, Prague -->
<mapZone other="Central Europe Standard Time" territory="001" type="Europe/Budapest"/>
<mapZone other="Central Europe Standard Time" territory="AL" type="Europe/Tirane"/>
<mapZone other="Central Europe Standard Time" territory="CZ" type="Europe/Prague"/>
<mapZone other="Central Europe Standard Time" territory="HU" type="Europe/Budapest"/>
<mapZone other="Central Europe Standard Time" territory="ME" type="Europe/Podgorica"/>
<mapZone other="Central Europe Standard Time" territory="RS" type="Europe/Belgrade"/>
<mapZone other="Central Europe Standard Time" territory="SI" type="Europe/Ljubljana"/>
<mapZone other="Central Europe Standard Time" territory="SK" type="Europe/Bratislava"/>
<!-- (UTC+01:00) Brussels, Copenhagen, Madrid, Paris -->
<mapZone other="Romance Standard Time" territory="001" type="Europe/Paris"/>
<mapZone other="Romance Standard Time" territory="BE" type="Europe/Brussels"/>
<mapZone other="Romance Standard Time" territory="DK" type="Europe/Copenhagen"/>
<mapZone other="Romance Standard Time" territory="ES" type="Europe/Madrid Africa/Ceuta"/>
<mapZone other="Romance Standard Time" territory="FR" type="Europe/Paris"/>
<!-- (UTC+01:00) Sarajevo, Skopje, Warsaw, Zagreb -->
<mapZone other="Central European Standard Time" territory="001" type="Europe/Warsaw"/>
<mapZone other="Central European Standard Time" territory="BA" type="Europe/Sarajevo"/>
<mapZone other="Central European Standard Time" territory="HR" type="Europe/Zagreb"/>
<mapZone other="Central European Standard Time" territory="MK" type="Europe/Skopje"/>
<mapZone other="Central European Standard Time" territory="PL" type="Europe/Warsaw"/>
<!-- (UTC+01:00) West Central Africa -->
<mapZone other="W. Central Africa Standard Time" territory="001" type="Africa/Lagos"/>
<mapZone other="W. Central Africa Standard Time" territory="AO" type="Africa/Luanda"/>
<mapZone other="W. Central Africa Standard Time" territory="BJ" type="Africa/Porto-Novo"/>
<mapZone other="W. Central Africa Standard Time" territory="CD" type="Africa/Kinshasa"/>
<mapZone other="W. Central Africa Standard Time" territory="CF" type="Africa/Bangui"/>
<mapZone other="W. Central Africa Standard Time" territory="CG" type="Africa/Brazzaville"/>
<mapZone other="W. Central Africa Standard Time" territory="CM" type="Africa/Douala"/>
<mapZone other="W. Central Africa Standard Time" territory="DZ" type="Africa/Algiers"/>
<mapZone other="W. Central Africa Standard Time" territory="GA" type="Africa/Libreville"/>
<mapZone other="W. Central Africa Standard Time" territory="GQ" type="Africa/Malabo"/>
<mapZone other="W. Central Africa Standard Time" territory="NE" type="Africa/Niamey"/>
<mapZone other="W. Central Africa Standard Time" territory="NG" type="Africa/Lagos"/>
<mapZone other="W. Central Africa Standard Time" territory="TD" type="Africa/Ndjamena"/>
<mapZone other="W. Central Africa Standard Time" territory="TN" type="Africa/Tunis"/>
<mapZone other="W. Central Africa Standard Time" territory="ZZ" type="Etc/GMT-1"/>
<!-- (UTC+01:00) Windhoek -->
<mapZone other="Namibia Standard Time" territory="001" type="Africa/Windhoek"/>
<mapZone other="Namibia Standard Time" territory="NA" type="Africa/Windhoek"/>
<!-- (UTC+02:00) Amman -->
<mapZone other="Jordan Standard Time" territory="001" type="Asia/Amman"/>
<mapZone other="Jordan Standard Time" territory="JO" type="Asia/Amman"/>
<!-- (UTC+02:00) Athens, Bucharest -->
<mapZone other="GTB Standard Time" territory="001" type="Europe/Bucharest"/>
<mapZone other="GTB Standard Time" territory="CY" type="Asia/Nicosia"/>
<mapZone other="GTB Standard Time" territory="GR" type="Europe/Athens"/>
<mapZone other="GTB Standard Time" territory="RO" type="Europe/Bucharest"/>
<!-- (UTC+02:00) Beirut -->
<mapZone other="Middle East Standard Time" territory="001" type="Asia/Beirut"/>
<mapZone other="Middle East Standard Time" territory="LB" type="Asia/Beirut"/>
<!-- (UTC+02:00) Cairo -->
<mapZone other="Egypt Standard Time" territory="001" type="Africa/Cairo"/>
<mapZone other="Egypt Standard Time" territory="EG" type="Africa/Cairo"/>
<!-- (UTC+02:00) Chisinau -->
<mapZone other="E. Europe Standard Time" territory="001" type="Europe/Chisinau"/>
<mapZone other="E. Europe Standard Time" territory="MD" type="Europe/Chisinau"/>
<!-- (UTC+02:00) Damascus -->
<mapZone other="Syria Standard Time" territory="001" type="Asia/Damascus"/>
<mapZone other="Syria Standard Time" territory="SY" type="Asia/Damascus"/>
<!-- (UTC+02:00) Gaza, Hebron -->
<mapZone other="West Bank Standard Time" territory="001" type="Asia/Hebron"/>
<mapZone other="West Bank Standard Time" territory="PS" type="Asia/Hebron Asia/Gaza"/>
<!-- (UTC+02:00) Harare, Pretoria -->
<mapZone other="South Africa Standard Time" territory="001" type="Africa/Johannesburg"/>
<mapZone other="South Africa Standard Time" territory="BI" type="Africa/Bujumbura"/>
<mapZone other="South Africa Standard Time" territory="BW" type="Africa/Gaborone"/>
<mapZone other="South Africa Standard Time" territory="CD" type="Africa/Lubumbashi"/>
<mapZone other="South Africa Standard Time" territory="LS" type="Africa/Maseru"/>
<mapZone other="South Africa Standard Time" territory="MW" type="Africa/Blantyre"/>
<mapZone other="South Africa Standard Time" territory="MZ" type="Africa/Maputo"/>
<mapZone other="South Africa Standard Time" territory="RW" type="Africa/Kigali"/>
<mapZone other="South Africa Standard Time" territory="SZ" type="Africa/Mbabane"/>
<mapZone other="South Africa Standard Time" territory="ZA" type="Africa/Johannesburg"/>
<mapZone other="South Africa Standard Time" territory="ZM" type="Africa/Lusaka"/>
<mapZone other="South Africa Standard Time" territory="ZW" type="Africa/Harare"/>
<mapZone other="South Africa Standard Time" territory="ZZ" type="Etc/GMT-2"/>
<!-- (UTC+02:00) Helsinki, Kyiv, Riga, Sofia, Tallinn, Vilnius -->
<mapZone other="FLE Standard Time" territory="001" type="Europe/Kiev"/>
<mapZone other="FLE Standard Time" territory="AX" type="Europe/Mariehamn"/>
<mapZone other="FLE Standard Time" territory="BG" type="Europe/Sofia"/>
<mapZone other="FLE Standard Time" territory="EE" type="Europe/Tallinn"/>
<mapZone other="FLE Standard Time" territory="FI" type="Europe/Helsinki"/>
<mapZone other="FLE Standard Time" territory="LT" type="Europe/Vilnius"/>
<mapZone other="FLE Standard Time" territory="LV" type="Europe/Riga"/>
<mapZone other="FLE Standard Time" territory="UA" type="Europe/Kiev Europe/Uzhgorod Europe/Zaporozhye"/>
<!-- (UTC+02:00) Istanbul -->
<mapZone other="Turkey Standard Time" territory="001" type="Europe/Istanbul"/>
<mapZone other="Turkey Standard Time" territory="TR" type="Europe/Istanbul"/>
<!-- (UTC+02:00) Jerusalem -->
<mapZone other="Israel Standard Time" territory="001" type="Asia/Jerusalem"/>
<mapZone other="Israel Standard Time" territory="IL" type="Asia/Jerusalem"/>
<!-- (UTC+02:00) Kaliningrad -->
<mapZone other="Kaliningrad Standard Time" territory="001" type="Europe/Kaliningrad"/>
<mapZone other="Kaliningrad Standard Time" territory="RU" type="Europe/Kaliningrad"/>
<!-- (UTC+02:00) Tripoli -->
<mapZone other="Libya Standard Time" territory="001" type="Africa/Tripoli"/>
<mapZone other="Libya Standard Time" territory="LY" type="Africa/Tripoli"/>
<!-- (UTC+03:00) Baghdad -->
<mapZone other="Arabic Standard Time" territory="001" type="Asia/Baghdad"/>
<mapZone other="Arabic Standard Time" territory="IQ" type="Asia/Baghdad"/>
<!-- (UTC+03:00) Kuwait, Riyadh -->
<mapZone other="Arab Standard Time" territory="001" type="Asia/Riyadh"/>
<mapZone other="Arab Standard Time" territory="BH" type="Asia/Bahrain"/>
<mapZone other="Arab Standard Time" territory="KW" type="Asia/Kuwait"/>
<mapZone other="Arab Standard Time" territory="QA" type="Asia/Qatar"/>
<mapZone other="Arab Standard Time" territory="SA" type="Asia/Riyadh"/>
<mapZone other="Arab Standard Time" territory="YE" type="Asia/Aden"/>
<!-- (UTC+03:00) Minsk -->
<mapZone other="Belarus Standard Time" territory="001" type="Europe/Minsk"/>
<mapZone other="Belarus Standard Time" territory="BY" type="Europe/Minsk"/>
<!-- (UTC+03:00) Moscow, St. Petersburg, Volgograd -->
<mapZone other="Russian Standard Time" territory="001" type="Europe/Moscow"/>
<mapZone other="Russian Standard Time" territory="RU" type="Europe/Moscow Europe/Kirov Europe/Volgograd"/>
<mapZone other="Russian Standard Time" territory="UA" type="Europe/Simferopol"/>
<!-- (UTC+03:00) Nairobi -->
<mapZone other="E. Africa Standard Time" territory="001" type="Africa/Nairobi"/>
<mapZone other="E. Africa Standard Time" territory="AQ" type="Antarctica/Syowa"/>
<mapZone other="E. Africa Standard Time" territory="DJ" type="Africa/Djibouti"/>
<mapZone other="E. Africa Standard Time" territory="ER" type="Africa/Asmera"/>
<mapZone other="E. Africa Standard Time" territory="ET" type="Africa/Addis_Ababa"/>
<mapZone other="E. Africa Standard Time" territory="KE" type="Africa/Nairobi"/>
<mapZone other="E. Africa Standard Time" territory="KM" type="Indian/Comoro"/>
<mapZone other="E. Africa Standard Time" territory="MG" type="Indian/Antananarivo"/>
<mapZone other="E. Africa Standard Time" territory="SD" type="Africa/Khartoum"/>
<mapZone other="E. Africa Standard Time" territory="SO" type="Africa/Mogadishu"/>
<mapZone other="E. Africa Standard Time" territory="SS" type="Africa/Juba"/>
<mapZone other="E. Africa Standard Time" territory="TZ" type="Africa/Dar_es_Salaam"/>
<mapZone other="E. Africa Standard Time" territory="UG" type="Africa/Kampala"/>
<mapZone other="E. Africa Standard Time" territory="YT" type="Indian/Mayotte"/>
<mapZone other="E. Africa Standard Time" territory="ZZ" type="Etc/GMT-3"/>
<!-- (UTC+03:30) Tehran -->
<mapZone other="Iran Standard Time" territory="001" type="Asia/Tehran"/>
<mapZone other="Iran Standard Time" territory="IR" type="Asia/Tehran"/>
<!-- (UTC+04:00) Abu Dhabi, Muscat -->
<mapZone other="Arabian Standard Time" territory="001" type="Asia/Dubai"/>
<mapZone other="Arabian Standard Time" territory="AE" type="Asia/Dubai"/>
<mapZone other="Arabian Standard Time" territory="OM" type="Asia/Muscat"/>
<mapZone other="Arabian Standard Time" territory="ZZ" type="Etc/GMT-4"/>
<!-- (UTC+04:00) Astrakhan, Ulyanovsk -->
<mapZone other="Astrakhan Standard Time" territory="001" type="Europe/Astrakhan"/>
<mapZone other="Astrakhan Standard Time" territory="RU" type="Europe/Astrakhan Europe/Ulyanovsk"/>
<!-- (UTC+04:00) Baku -->
<mapZone other="Azerbaijan Standard Time" territory="001" type="Asia/Baku"/>
<mapZone other="Azerbaijan Standard Time" territory="AZ" type="Asia/Baku"/>
<!-- (UTC+04:00) Izhevsk, Samara -->
<mapZone other="Russia Time Zone 3" territory="001" type="Europe/Samara"/>
<mapZone other="Russia Time Zone 3" territory="RU" type="Europe/Samara"/>
<!-- (UTC+04:00) Port Louis -->
<mapZone other="Mauritius Standard Time" territory="001" type="Indian/Mauritius"/>
<mapZone other="Mauritius Standard Time" territory="MU" type="Indian/Mauritius"/>
<mapZone other="Mauritius Standard Time" territory="RE" type="Indian/Reunion"/>
<mapZone other="Mauritius Standard Time" territory="SC" type="Indian/Mahe"/>
<!-- (UTC+04:00) Tbilisi -->
<mapZone other="Georgian Standard Time" territory="001" type="Asia/Tbilisi"/>
<mapZone other="Georgian Standard Time" territory="GE" type="Asia/Tbilisi"/>
<!-- (UTC+04:00) Yerevan -->
<mapZone other="Caucasus Standard Time" territory="001" type="Asia/Yerevan"/>
<mapZone other="Caucasus Standard Time" territory="AM" type="Asia/Yerevan"/>
<!-- (UTC+04:30) Kabul -->
<mapZone other="Afghanistan Standard Time" territory="001" type="Asia/Kabul"/>
<mapZone other="Afghanistan Standard Time" territory="AF" type="Asia/Kabul"/>
<!-- (UTC+05:00) Ashgabat, Tashkent -->
<mapZone other="West Asia Standard Time" territory="001" type="Asia/Tashkent"/>
<mapZone other="West Asia Standard Time" territory="AQ" type="Antarctica/Mawson"/>
<mapZone other="West Asia Standard Time" territory="KZ" type="Asia/Oral Asia/Aqtau Asia/Aqtobe"/>
<mapZone other="West Asia Standard Time" territory="MV" type="Indian/Maldives"/>
<mapZone other="West Asia Standard Time" territory="TF" type="Indian/Kerguelen"/>
<mapZone other="West Asia Standard Time" territory="TJ" type="Asia/Dushanbe"/>
<mapZone other="West Asia Standard Time" territory="TM" type="Asia/Ashgabat"/>
<mapZone other="West Asia Standard Time" territory="UZ" type="Asia/Tashkent Asia/Samarkand"/>
<mapZone other="West Asia Standard Time" territory="ZZ" type="Etc/GMT-5"/>
<!-- (UTC+05:00) Ekaterinburg -->
<mapZone other="Ekaterinburg Standard Time" territory="001" type="Asia/Yekaterinburg"/>
<mapZone other="Ekaterinburg Standard Time" territory="RU" type="Asia/Yekaterinburg"/>
<!-- (UTC+05:00) Islamabad, Karachi -->
<mapZone other="Pakistan Standard Time" territory="001" type="Asia/Karachi"/>
<mapZone other="Pakistan Standard Time" territory="PK" type="Asia/Karachi"/>
<!-- (UTC+05:30) Chennai, Kolkata, Mumbai, New Delhi -->
<mapZone other="India Standard Time" territory="001" type="Asia/Calcutta"/>
<mapZone other="India Standard Time" territory="IN" type="Asia/Calcutta"/>
<!-- (UTC+05:30) Sri Jayawardenepura -->
<mapZone other="Sri Lanka Standard Time" territory="001" type="Asia/Colombo"/>
<mapZone other="Sri Lanka Standard Time" territory="LK" type="Asia/Colombo"/>
<!-- (UTC+05:45) Kathmandu -->
<mapZone other="Nepal Standard Time" territory="001" type="Asia/Katmandu"/>
<mapZone other="Nepal Standard Time" territory="NP" type="Asia/Katmandu"/>
<!-- (UTC+06:00) Astana -->
<mapZone other="Central Asia Standard Time" territory="001" type="Asia/Almaty"/>
<mapZone other="Central Asia Standard Time" territory="AQ" type="Antarctica/Vostok"/>
<mapZone other="Central Asia Standard Time" territory="CN" type="Asia/Urumqi"/>
<mapZone other="Central Asia Standard Time" territory="IO" type="Indian/Chagos"/>
<mapZone other="Central Asia Standard Time" territory="KG" type="Asia/Bishkek"/>
<mapZone other="Central Asia Standard Time" territory="KZ" type="Asia/Almaty Asia/Qyzylorda"/>
<mapZone other="Central Asia Standard Time" territory="ZZ" type="Etc/GMT-6"/>
<!-- (UTC+06:00) Dhaka -->
<mapZone other="Bangladesh Standard Time" territory="001" type="Asia/Dhaka"/>
<mapZone other="Bangladesh Standard Time" territory="BD" type="Asia/Dhaka"/>
<mapZone other="Bangladesh Standard Time" territory="BT" type="Asia/Thimphu"/>
<!-- (UTC+06:00) Omsk -->
<mapZone other="Omsk Standard Time" territory="001" type="Asia/Omsk"/>
<mapZone other="Omsk Standard Time" territory="RU" type="Asia/Omsk"/>
<!-- (UTC+06:30) Yangon (Rangoon) -->
<mapZone other="Myanmar Standard Time" territory="001" type="Asia/Rangoon"/>
<mapZone other="Myanmar Standard Time" territory="CC" type="Indian/Cocos"/>
<mapZone other="Myanmar Standard Time" territory="MM" type="Asia/Rangoon"/>
<!-- (UTC+07:00) Bangkok, Hanoi, Jakarta -->
<mapZone other="SE Asia Standard Time" territory="001" type="Asia/Bangkok"/>
<mapZone other="SE Asia Standard Time" territory="AQ" type="Antarctica/Davis"/>
<mapZone other="SE Asia Standard Time" territory="CX" type="Indian/Christmas"/>
<mapZone other="SE Asia Standard Time" territory="ID" type="Asia/Jakarta Asia/Pontianak"/>
<mapZone other="SE Asia Standard Time" territory="KH" type="Asia/Phnom_Penh"/>
<mapZone other="SE Asia Standard Time" territory="LA" type="Asia/Vientiane"/>
<mapZone other="SE Asia Standard Time" territory="TH" type="Asia/Bangkok"/>
<mapZone other="SE Asia Standard Time" territory="VN" type="Asia/Saigon"/>
<mapZone other="SE Asia Standard Time" territory="ZZ" type="Etc/GMT-7"/>
<!-- (UTC+07:00) Barnaul, Gorno-Altaysk -->
<mapZone other="Altai Standard Time" territory="001" type="Asia/Barnaul"/>
<mapZone other="Altai Standard Time" territory="RU" type="Asia/Barnaul"/>
<!-- (UTC+07:00) Hovd -->
<mapZone other="W. Mongolia Standard Time" territory="001" type="Asia/Hovd"/>
<mapZone other="W. Mongolia Standard Time" territory="MN" type="Asia/Hovd"/>
<!-- (UTC+07:00) Krasnoyarsk -->
<mapZone other="North Asia Standard Time" territory="001" type="Asia/Krasnoyarsk"/>
<mapZone other="North Asia Standard Time" territory="RU" type="Asia/Krasnoyarsk Asia/Novokuznetsk"/>
<!-- (UTC+07:00) Novosibirsk -->
<mapZone other="N. Central Asia Standard Time" territory="001" type="Asia/Novosibirsk"/>
<mapZone other="N. Central Asia Standard Time" territory="RU" type="Asia/Novosibirsk"/>
<!-- (UTC+07:00) Tomsk -->
<mapZone other="Tomsk Standard Time" territory="001" type="Asia/Tomsk"/>
<mapZone other="Tomsk Standard Time" territory="RU" type="Asia/Tomsk"/>
<!-- (UTC+08:00) Beijing, Chongqing, Hong Kong, Urumqi -->
<mapZone other="China Standard Time" territory="001" type="Asia/Shanghai"/>
<mapZone other="China Standard Time" territory="CN" type="Asia/Shanghai"/>
<mapZone other="China Standard Time" territory="HK" type="Asia/Hong_Kong"/>
<mapZone other="China Standard Time" territory="MO" type="Asia/Macau"/>
<!-- (UTC+08:00) Irkutsk -->
<mapZone other="North Asia East Standard Time" territory="001" type="Asia/Irkutsk"/>
<mapZone other="North Asia East Standard Time" territory="RU" type="Asia/Irkutsk"/>
<!-- (UTC+08:00) Kuala Lumpur, Singapore -->
<mapZone other="Singapore Standard Time" territory="001" type="Asia/Singapore"/>
<mapZone other="Singapore Standard Time" territory="BN" type="Asia/Brunei"/>
<mapZone other="Singapore Standard Time" territory="ID" type="Asia/Makassar"/>
<mapZone other="Singapore Standard Time" territory="MY" type="Asia/Kuala_Lumpur Asia/Kuching"/>
<mapZone other="Singapore Standard Time" territory="PH" type="Asia/Manila"/>
<mapZone other="Singapore Standard Time" territory="SG" type="Asia/Singapore"/>
<mapZone other="Singapore Standard Time" territory="ZZ" type="Etc/GMT-8"/>
<!-- (UTC+08:00) Perth -->
<mapZone other="W. Australia Standard Time" territory="001" type="Australia/Perth"/>
<mapZone other="W. Australia Standard Time" territory="AU" type="Australia/Perth"/>
<!-- (UTC+08:00) Taipei -->
<mapZone other="Taipei Standard Time" territory="001" type="Asia/Taipei"/>
<mapZone other="Taipei Standard Time" territory="TW" type="Asia/Taipei"/>
<!-- (UTC+08:00) Ulaanbaatar -->
<mapZone other="Ulaanbaatar Standard Time" territory="001" type="Asia/Ulaanbaatar"/>
<mapZone other="Ulaanbaatar Standard Time" territory="MN" type="Asia/Ulaanbaatar Asia/Choibalsan"/>
<!-- (UTC+08:30) Pyongyang -->
<mapZone other="North Korea Standard Time" territory="001" type="Asia/Pyongyang"/>
<mapZone other="North Korea Standard Time" territory="KP" type="Asia/Pyongyang"/>
<!-- (UTC+08:45) Eucla -->
<mapZone other="Aus Central W. Standard Time" territory="001" type="Australia/Eucla"/>
<mapZone other="Aus Central W. Standard Time" territory="AU" type="Australia/Eucla"/>
<!-- (UTC+09:00) Chita -->
<mapZone other="Transbaikal Standard Time" territory="001" type="Asia/Chita"/>
<mapZone other="Transbaikal Standard Time" territory="RU" type="Asia/Chita"/>
<!-- (UTC+09:00) Osaka, Sapporo, Tokyo -->
<mapZone other="Tokyo Standard Time" territory="001" type="Asia/Tokyo"/>
<mapZone other="Tokyo Standard Time" territory="ID" type="Asia/Jayapura"/>
<mapZone other="Tokyo Standard Time" territory="JP" type="Asia/Tokyo"/>
<mapZone other="Tokyo Standard Time" territory="PW" type="Pacific/Palau"/>
<mapZone other="Tokyo Standard Time" territory="TL" type="Asia/Dili"/>
<mapZone other="Tokyo Standard Time" territory="ZZ" type="Etc/GMT-9"/>
<!-- (UTC+09:00) Seoul -->
<mapZone other="Korea Standard Time" territory="001" type="Asia/Seoul"/>
<mapZone other="Korea Standard Time" territory="KR" type="Asia/Seoul"/>
<!-- (UTC+09:00) Yakutsk -->
<mapZone other="Yakutsk Standard Time" territory="001" type="Asia/Yakutsk"/>
<mapZone other="Yakutsk Standard Time" territory="RU" type="Asia/Yakutsk Asia/Khandyga"/>
<!-- (UTC+09:30) Adelaide -->
<mapZone other="Cen. Australia Standard Time" territory="001" type="Australia/Adelaide"/>
<mapZone other="Cen. Australia Standard Time" territory="AU" type="Australia/Adelaide Australia/Broken_Hill"/>
<!-- (UTC+09:30) Darwin -->
<mapZone other="AUS Central Standard Time" territory="001" type="Australia/Darwin"/>
<mapZone other="AUS Central Standard Time" territory="AU" type="Australia/Darwin"/>
<!-- (UTC+10:00) Brisbane -->
<mapZone other="E. Australia Standard Time" territory="001" type="Australia/Brisbane"/>
<mapZone other="E. Australia Standard Time" territory="AU" type="Australia/Brisbane Australia/Lindeman"/>
<!-- (UTC+10:00) Canberra, Melbourne, Sydney -->
<mapZone other="AUS Eastern Standard Time" territory="001" type="Australia/Sydney"/>
<mapZone other="AUS Eastern Standard Time" territory="AU" type="Australia/Sydney Australia/Melbourne"/>
<!-- (UTC+10:00) Guam, Port Moresby -->
<mapZone other="West Pacific Standard Time" territory="001" type="Pacific/Port_Moresby"/>
<mapZone other="West Pacific Standard Time" territory="AQ" type="Antarctica/DumontDUrville"/>
<mapZone other="West Pacific Standard Time" territory="FM" type="Pacific/Truk"/>
<mapZone other="West Pacific Standard Time" territory="GU" type="Pacific/Guam"/>
<mapZone other="West Pacific Standard Time" territory="MP" type="Pacific/Saipan"/>
<mapZone other="West Pacific Standard Time" territory="PG" type="Pacific/Port_Moresby"/>
<mapZone other="West Pacific Standard Time" territory="ZZ" type="Etc/GMT-10"/>
<!-- (UTC+10:00) Hobart -->
<mapZone other="Tasmania Standard Time" territory="001" type="Australia/Hobart"/>
<mapZone other="Tasmania Standard Time" territory="AU" type="Australia/Hobart Australia/Currie"/>
<!-- (UTC+10:00) Vladivostok -->
<mapZone other="Vladivostok Standard Time" territory="001" type="Asia/Vladivostok"/>
<mapZone other="Vladivostok Standard Time" territory="RU" type="Asia/Vladivostok Asia/Ust-Nera"/>
<!-- (UTC+10:30) Lord Howe Island -->
<mapZone other="Lord Howe Standard Time" territory="001" type="Australia/Lord_Howe"/>
<mapZone other="Lord Howe Standard Time" territory="AU" type="Australia/Lord_Howe"/>
<!-- (UTC+11:00) Bougainville Island -->
<mapZone other="Bougainville Standard Time" territory="001" type="Pacific/Bougainville"/>
<mapZone other="Bougainville Standard Time" territory="PG" type="Pacific/Bougainville"/>
<!-- (UTC+11:00) Chokurdakh -->
<mapZone other="Russia Time Zone 10" territory="001" type="Asia/Srednekolymsk"/>
<mapZone other="Russia Time Zone 10" territory="RU" type="Asia/Srednekolymsk"/>
<!-- (UTC+11:00) Magadan -->
<mapZone other="Magadan Standard Time" territory="001" type="Asia/Magadan"/>
<mapZone other="Magadan Standard Time" territory="RU" type="Asia/Magadan"/>
<!-- (UTC+11:00) Norfolk Island -->
<mapZone other="Norfolk Standard Time" territory="001" type="Pacific/Norfolk"/>
<mapZone other="Norfolk Standard Time" territory="NF" type="Pacific/Norfolk"/>
<!-- (UTC+11:00) Sakhalin -->
<mapZone other="Sakhalin Standard Time" territory="001" type="Asia/Sakhalin"/>
<mapZone other="Sakhalin Standard Time" territory="RU" type="Asia/Sakhalin"/>
<!-- (UTC+11:00) Solomon Is., New Caledonia -->
<mapZone other="Central Pacific Standard Time" territory="001" type="Pacific/Guadalcanal"/>
<mapZone other="Central Pacific Standard Time" territory="AQ" type="Antarctica/Casey"/>
<mapZone other="Central Pacific Standard Time" territory="AU" type="Antarctica/Macquarie"/>
<mapZone other="Central Pacific Standard Time" territory="FM" type="Pacific/Ponape Pacific/Kosrae"/>
<mapZone other="Central Pacific Standard Time" territory="NC" type="Pacific/Noumea"/>
<mapZone other="Central Pacific Standard Time" territory="SB" type="Pacific/Guadalcanal"/>
<mapZone other="Central Pacific Standard Time" territory="VU" type="Pacific/Efate"/>
<mapZone other="Central Pacific Standard Time" territory="ZZ" type="Etc/GMT-11"/>
<!-- (UTC+12:00) Anadyr, Petropavlovsk-Kamchatsky -->
<mapZone other="Russia Time Zone 11" territory="001" type="Asia/Kamchatka"/>
<mapZone other="Russia Time Zone 11" territory="RU" type="Asia/Kamchatka Asia/Anadyr"/>
<!-- (UTC+12:00) Auckland, Wellington -->
<mapZone other="New Zealand Standard Time" territory="001" type="Pacific/Auckland"/>
<mapZone other="New Zealand Standard Time" territory="AQ" type="Antarctica/McMurdo"/>
<mapZone other="New Zealand Standard Time" territory="NZ" type="Pacific/Auckland"/>
<!-- (UTC+12:00) Coordinated Universal Time+12 -->
<mapZone other="UTC+12" territory="001" type="Etc/GMT-12"/>
<mapZone other="UTC+12" territory="KI" type="Pacific/Tarawa"/>
<mapZone other="UTC+12" territory="MH" type="Pacific/Majuro Pacific/Kwajalein"/>
<mapZone other="UTC+12" territory="NR" type="Pacific/Nauru"/>
<mapZone other="UTC+12" territory="TV" type="Pacific/Funafuti"/>
<mapZone other="UTC+12" territory="UM" type="Pacific/Wake"/>
<mapZone other="UTC+12" territory="WF" type="Pacific/Wallis"/>
<mapZone other="UTC+12" territory="ZZ" type="Etc/GMT-12"/>
<!-- (UTC+12:00) Fiji -->
<mapZone other="Fiji Standard Time" territory="001" type="Pacific/Fiji"/>
<mapZone other="Fiji Standard Time" territory="FJ" type="Pacific/Fiji"/>
<!-- (UTC+12:45) Chatham Islands -->
<mapZone other="Chatham Islands Standard Time" territory="001" type="Pacific/Chatham"/>
<mapZone other="Chatham Islands Standard Time" territory="NZ" type="Pacific/Chatham"/>
<!-- (UTC+13:00) Nuku'alofa -->
<mapZone other="Tonga Standard Time" territory="001" type="Pacific/Tongatapu"/>
<mapZone other="Tonga Standard Time" territory="KI" type="Pacific/Enderbury"/>
<mapZone other="Tonga Standard Time" territory="TK" type="Pacific/Fakaofo"/>
<mapZone other="Tonga Standard Time" territory="TO" type="Pacific/Tongatapu"/>
<mapZone other="Tonga Standard Time" territory="ZZ" type="Etc/GMT-13"/>
<!-- (UTC+13:00) Samoa -->
<mapZone other="Samoa Standard Time" territory="001" type="Pacific/Apia"/>
<mapZone other="Samoa Standard Time" territory="WS" type="Pacific/Apia"/>
<!-- (UTC+14:00) Kiritimati Island -->
<mapZone other="Line Islands Standard Time" territory="001" type="Pacific/Kiritimati"/>
<mapZone other="Line Islands Standard Time" territory="KI" type="Pacific/Kiritimati"/>
<mapZone other="Line Islands Standard Time" territory="ZZ" type="Etc/GMT-14"/>
</mapTimezones>
</windowsZones>
</supplementalData>

View File

@@ -1,31 +0,0 @@
3.03 28 May 1999
- Added cows/tux.cow, as suggested by xmanoel@i.am
- Compatibility with 5.6.0, due to a change in qw().
- Renamed devil.cow to daemon.cow, since I know better. :-)
3.02 04 November 1999
- Fixed boneheaded code placement so that cowsay -l actually works.
3.01 01 November 1999
- Fixed compatibility issues between the Text::Wrap module
that changed between 5.005_02 and 5.005_03.
- Fixed tab expansion issues with Text::Tabs.
3.0 13 April 1999, released 14 August 1999
- Rewritten into Perl 5 and presented to the world.
-- Not present in CVS from here on down --
2.x Date?
- Arbitrary messages.
- Figlet support (-n).
- Line wrap length (-w).
- Multiple pre-set expressions.
- Better arg parsing loop.
- Message from stdin or command line.
1.0 Date?
- SUBJECT is VERB OBJECT
$Id: ChangeLog,v 1.4 2000/05/29 17:55:24 tony Exp $
This file is part of cowsay. (c) 1999-2000 Tony Monroe.

View File

@@ -1,15 +0,0 @@
=================
Installing cowsay
=================
If you really want to get things installed a nice and pretty way,
sh install.sh
It will ask approximately one question. If you can't answer it,
you need serious help.
If the install goes well, you can start cowing immediately! Just
be sure to read the manual page first...
$Id: INSTALL,v 1.1 1999/08/14 08:03:17 tony Exp $

View File

@@ -1,36 +0,0 @@
==============
cowsay License
==============
cowsay is distributed under the same licensing terms as Perl: the
Artistic License or the GNU General Public License. If you don't
want to track down these licenses and read them for yourself, use
the parts that I'd prefer:
(0) I wrote it and you didn't.
(1) Give credit where credit is due if you borrow the code for some
other purpose.
(2) If you have any bugfixes or suggestions, please notify me so
that I may incorporate them.
(3) If you try to make money off of cowsay, you suck.
===============
cowsay Legalese
===============
(0) Copyright (c) 1999 Tony Monroe. All rights reserved. All
lefts may or may not be reversed at my discretion.
(1) This software package can be freely redistributed or modified
under the terms described above in the "cowsay License" section
of this file.
(2) cowsay is provided "as is," with no warranties whatsoever,
expressed or implied. If you want some implied warranty about
merchantability and/or fitness for a particular purpose, you will
not find it here, because there is no such thing here.
(3) I hate legalese.

View File

@@ -1,11 +0,0 @@
ChangeLog Changes to recent versions.
INSTALL Instructions for installing cowsay.
LICENSE The license for use and redistribution of cowsay.
MANIFEST This file.
README Read this first. Really.
Wrap.pm.diff Diff for Text/Wrap.pm.
cows/* Support files used by cowsay.
cowsay Main cowsay executable.
cowsay.1 Main cowsay manual page.
install.sh cowsay installation script.
pgp_public_key.txt Verify the signature file with this key.

View File

@@ -1,38 +0,0 @@
===========
cowsay 3.03
===========
cowsay is a configurable talking cow, written in Perl. It operates
much as the figlet program does, and it written in the same spirit
of silliness.
cowsay is actually a pretty old program. It has not really been
released before, and I am releasing it in the hope that someone
other than myself will be amused by it.
The first major version of cowsay had one cow and one message
template: $foo is $verb $bar. Not very flexible, but people managed
to do pretty interesting things with it. The second major version
scrapped many of the limitations of the first, by allowing arbitrary
messages, multiple cowfiles, and even support for cows talking in
figlet. The third version was a rewrite of the second into Perl
5, whereupon the code got a lot smaller and more manageable. :-)
If you are using Perl 5.004, you may have problems with Text::Wrap.
(Yeesh, this module changes more than it should...) I've included
a diff for the Text::Wrap (version 97.011701) that is shipped with
5.004_04; the concept is simple enough that even older Perls can
take advantage of this silly little patch; if there is a "sub fill"
in the documentation for the module, copy it to a more useful
section of that file. If not, just take "sub fill" wholesale from
the patch. Oh, and consider upgrading to 5.005_03 or later.
Please. You'll like it, I promise.
To install cowsay, consult the INSTALL file in this directory.
For the terms and conditions of use, consult the LICENSE file in
this directory.
-- Tony Monroe (tony@nog.net)
$Id: README,v 1.3 2000/05/28 06:24:46 tony Exp $

View File

@@ -1,47 +0,0 @@
*** Wrap.pm.in Thu May 22 00:21:42 1997
--- Wrap.pm Fri Nov 12 10:00:15 1999
***************
*** 3,9 ****
require Exporter;
@ISA = (Exporter);
! @EXPORT = qw(wrap);
@EXPORT_OK = qw($columns);
$VERSION = 97.011701;
--- 3,9 ----
require Exporter;
@ISA = (Exporter);
! @EXPORT = qw(wrap fill);
@EXPORT_OK = qw($columns);
$VERSION = 97.011701;
***************
*** 66,71 ****
--- 66,90 ----
print "-----------$r---------\n" if $debug;;
return $r;
+ }
+
+ ## Copied up from below.
+ sub fill
+ {
+ my ($ip, $xp, @raw) = @_;
+ my @para;
+ my $pp;
+
+ for $pp (split(/\n\s+/, join("\n",@raw))) {
+ $pp =~ s/\s+/ /g;
+ my $x = wrap($ip, $xp, $pp);
+ push(@para, $x);
+ }
+
+ # if paragraph_indent is the same as line_indent,
+ # separate paragraphs with blank lines
+
+ return join ($ip eq $xp ? "\n\n" : "\n", @para);
}
1;

View File

@@ -1,10 +0,0 @@
##
## A cute little wabbit
##
$the_cow = <<EOC;
$thoughts
$thoughts \\
\\ /\\
( )
.( o ).
EOC

View File

@@ -1,14 +0,0 @@
##
## A cowering cow
##
$the_cow = <<EOC;
$thoughts
$thoughts
,__, | |
(oo)\\| |___
(__)\\| | )\\_
| |_w | \\
| | || *
Cower....
EOC

View File

@@ -1,24 +0,0 @@
##
## 4.4 >> 5.4
##
$the_cow = <<EOC;
$thoughts , ,
$thoughts /( )`
$thoughts \\ \\___ / |
/- _ `-/ '
(/\\/ \\ \\ /\\
/ / | ` \\
O O ) / |
`-^--'`< '
(_.) _ ) /
`.___/` /
`-----' /
<----. __ / __ \\
<----|====O)))==) \\) /====
<----' `--' `.__,' \\
| |
\\ /
______( (_ / \\______
,' ,-----' | \\
`--{__________) \\/
EOC

View File

@@ -1,7 +0,0 @@
$the_cow = <<"EOC";
$thoughts ^__^
$thoughts ($eyes)\\_______
(__)\\ )\\/\\
$tongue ||----w |
|| ||
EOC

View File

@@ -1,21 +0,0 @@
##
## A dragon smiting a cow, possible credit to kube@csua.berkeley.edu
##
$the_cow = <<EOC;
$thoughts ^ /^
$thoughts / \\ // \\
$thoughts |\\___/| / \\// .\\
$thoughts /O O \\__ / // | \\ \\ *----*
/ / \\/_/ // | \\ \\ \\ |
\@___\@` \\/_ // | \\ \\ \\/\\ \\
0/0/| \\/_ // | \\ \\ \\ \\
0/0/0/0/| \\/// | \\ \\ | |
0/0/0/0/0/_|_ / ( // | \\ _\\ | /
0/0/0/0/0/0/`/,_ _ _/ ) ; -. | _ _\\.-~ / /
,-} _ *-.|.-~-. .~ ~
\\ \\__/ `/\\ / ~-. _ .-~ /
\\____($eyes) *. } { /
( (--) .----~-.\\ \\-` .~
//__\\\\ \\__ Ack! ///.----..< \\ _ -~
// \\\\ ///-._ _ _ _ _ _ _{^ - - - - ~
EOC

View File

@@ -1,21 +0,0 @@
##
## The Whitespace Dragon
##
$the_cow = <<EOC;
$thoughts / \\ //\\
$thoughts |\\___/| / \\// \\\\
/0 0 \\__ / // | \\ \\
/ / \\/_/ // | \\ \\
\@_^_\@'/ \\/_ // | \\ \\
//_^_/ \\/_ // | \\ \\
( //) | \\/// | \\ \\
( / /) _|_ / ) // | \\ _\\
( // /) '/,_ _ _/ ( ; -. | _ _\\.-~ .-~~~^-.
(( / / )) ,-{ _ `-.|.-~-. .~ `.
(( // / )) '/\\ / ~-. _ .-~ .-~^-. \\
(( /// )) `. { } / \\ \\
(( / )) .----~-.\\ \\-' .~ \\ `. \\^-.
///.----..> \\ _ -~ `. ^-` ^-_
///-._ _ _ _ _ _ _}^ - - - - ~ ~-- ,.-~
/.-~
EOC

View File

@@ -1,15 +0,0 @@
##
## An elephant out and about
##
$the_cow = <<EOC;
$thoughts /\\ ___ /\\
$thoughts // \\/ \\/ \\\\
(( O O ))
\\\\ / \\ //
\\/ | | \\/
| | | |
| | | |
| o |
| | | |
|m| |m|
EOC

View File

@@ -1,16 +0,0 @@
##
## Evil-looking eyes
##
$the_cow = <<EOC;
$thoughts
$thoughts
.::!!!!!!!:.
.!!!!!:. .:!!!!!!!!!!!!
~~~~!!!!!!. .:!!!!!!!!!UWWW\$\$\$
:\$\$NWX!!: .:!!!!!!XUWW\$\$\$\$\$\$\$\$\$P
\$\$\$\$\$##WX!: .<!!!!UW\$\$\$\$" \$\$\$\$\$\$\$\$#
\$\$\$\$\$ \$\$\$UX :!!UW\$\$\$\$\$\$\$\$\$ 4\$\$\$\$\$*
^\$\$\$B \$\$\$\$\\ \$\$\$\$\$\$\$\$\$\$\$\$ d\$\$R"
"*\$bd\$\$\$\$ '*\$\$\$\$\$\$\$\$\$\$\$o+#"
"""" """""""
EOC

View File

@@ -1,15 +0,0 @@
##
## The flaming sheep, contributed by Geordan Rosario (geordan@csua.berkeley.edu)
##
$the_cow = <<EOC;
$thoughts . . .
$thoughts . . . ` ,
$thoughts .; . : .' : : : .
$thoughts i..`: i` i.i.,i i .
$thoughts `,--.|i |i|ii|ii|i:
U${eyes}U\\.'\@\@\@\@\@\@`.||'
\\__/(\@\@\@\@\@\@\@\@\@\@)'
(\@\@\@\@\@\@\@\@)
`YY~~~~YY'
|| ||
EOC

View File

@@ -1,12 +0,0 @@
##
## Go stick yer head in a cow.
##
$the_cow = <<EOC;
$thoughts
$thoughts
^__^ /
($eyes)\\_______/ _________
(__)\\ )=( ____|_ \\_____
$tongue ||----w | \\ \\ \\_____ |
|| || || ||
EOC

View File

@@ -1,12 +0,0 @@
##
## A kitten of sorts, I think
##
$the_cow = <<EOC;
$thoughts
$thoughts
("`-' '-/") .___..--' ' "`-._
` *_ * ) `-. ( ) .`-.__. `)
(_Y_.) ' ._ ) `._` ; `` -. .-'
_.. `--'_..-_/ /--' _ .' ,4
( i l ),-'' ( l i),' ( ( ! .-'
EOC

View File

@@ -1,12 +0,0 @@
##
## From the canonical koala collection
##
$the_cow = <<EOC;
$thoughts
$thoughts
___
{~._.~}
( Y )
()~*~()
(_)-(_)
EOC

View File

@@ -1,15 +0,0 @@
##
## A meowing tiger?
##
$the_cow = <<EOC;
$thoughts
$thoughts , _ ___.--'''`--''//-,-_--_.
\\`"' ` || \\\\ \\ \\\\/ / // / ,-\\\\`,_
/'` \\ \\ || Y | \\|/ / // / - |__ `-,
/\@"\\ ` \\ `\\ | | ||/ // | \\/ \\ `-._`-,_.,
/ _.-. `.-\\,___/\\ _/|_/_\\_\\/|_/ | `-._._)
`-'``/ / | // \\__/\\__ / \\__/ \\
`-' /-\\/ | -| \\__ \\ |-' |
__/\\ / _/ \\/ __,-' ) ,' _|'
(((__/(((_.' ((___..-'((__,'
EOC

View File

@@ -1,14 +0,0 @@
##
## MOOfasa.
##
$the_cow = <<EOC;
$thoughts ____
$thoughts / \\
| ^__^ |
| ($eyes) |______
| (__) | )\\/\\
\\____/|----w |
|| ||
Moofasa
EOC

View File

@@ -1,9 +0,0 @@
$the_cow = <<EOC;
$thoughts
$thoughts \\_\\_ _/_/
$thoughts \\__/
($eyes)\\_______
(__)\\ )\\/\\
$tongue ||----w |
|| ||
EOC

View File

@@ -1,10 +0,0 @@
##
## A mutilated cow, from aspolito@csua.berkeley.edu
##
$the_cow = <<EOC;
$thoughts \\_______
v__v $thoughts \\ O )
($eyes) ||----w |
(__) || || \\/\\
$tongue
EOC

Some files were not shown because too many files have changed in this diff Show More