diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 00000000000..07cf26a7074 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,12 @@ +.git/ +.github/ +build/ +.editorconfig +.gitattributes +.gitignore +CHANGELOG.md +CODE_OF_CONDUCT.md +deploy.sh +font-selection.json +README.md +Vagrantfile diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000000..1692977cec8 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,18 @@ +# EditorConfig is awesome: https://EditorConfig.org + +# Top-most EditorConfig file +root = true + +# Unix-style newlines with a newline ending every file +[*] +end_of_line = lf +insert_final_newline = true +indent_style = space +indent_size = 2 +trim_trailing_whitespace = true + +[*.rb] +charset = utf-8 + +[*.md] +trim_trailing_whitespace = false diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000000..3069c432317 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +source/javascripts/lib/* linguist-vendored diff --git a/.github/ISSUE_TEMPLATE/bug.md b/.github/ISSUE_TEMPLATE/bug.md new file mode 100644 index 00000000000..43305a233da --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug.md @@ -0,0 +1,22 @@ +--- +name: Report a Bug +about: Create a report to help us improve +title: '' +labels: '' +assignees: '' + +--- + +**Bug Description** +A clear and concise description of what the bug is and how to reproduce it. + +**Screenshots** +If applicable, add screenshots to help explain your problem. + +**Browser (please complete the following information):** + - OS: [e.g. iOS] + - Browser [e.g. chrome, safari] + - Version [e.g. 22] + +**Last upstream Slate commit (run `git log --author="\(Robert Lord\)\|\(Matthew Peveler\)\|\(Mike Ralphson\)" | head -n 1`):** +Put the commit hash here diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 00000000000..16f4beed616 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,5 @@ +blank_issues_enabled: false +contact_links: + - name: Questions, Ideas, Discussions + url: https://github.com/slatedocs/slate/discussions + about: Ask and answer questions, and propose new features. diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 00000000000..151e45d78cd --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,5 @@ + \ No newline at end of file diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000000..b70e29a4788 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,9 @@ +version: 2 +updates: +- package-ecosystem: bundler + directory: "/" + schedule: + interval: monthly + open-pull-requests-limit: 10 + target-branch: master + versioning-strategy: increase-if-necessary diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 00000000000..660abfb0491 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,39 @@ +name: Build + +on: + push: + branches: [ '*' ] + pull_request: + branches: [ '*' ] + +jobs: + test: + # See https://github.com/oyindonesia/oybayar-docs/pull/119 about the reason of using 20.04 + runs-on: ubuntu-22.04 + + strategy: + matrix: + ruby-version: [2.6, 2.7, '3.0', 3.1, 3.2] + + steps: + - uses: actions/checkout@v2 + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: ${{ matrix.ruby-version }} + + - uses: actions/cache@v3 + with: + path: vendor/bundle + key: gems-${{ runner.os }}-${{ matrix.ruby-version }}-${{ hashFiles('**/Gemfile.lock') }} + restore-keys: | + gems-${{ runner.os }}-${{ matrix.ruby-version }}- + gems-${{ runner.os }}- + + - run: bundle config set deployment 'true' + - name: bundle install + run: | + bundle config path vendor/bundle + bundle install --jobs 4 --retry 3 + + - run: bundle exec middleman build diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 00000000000..f96738230d2 --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,45 @@ +name: Deploy + +on: + push: + branches: [ 'master' ] + +jobs: + deploy: + permissions: + contents: write + + # See https://github.com/oyindonesia/oybayar-docs/pull/119 about the reason of using 20.04 + runs-on: ubuntu-22.04 + env: + ruby-version: 2.6 + + steps: + - uses: actions/checkout@v2 + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: ${{ env.ruby-version }} + + - uses: actions/cache@v3 + with: + path: vendor/bundle + key: gems-${{ runner.os }}-${{ env.ruby-version }}-${{ hashFiles('**/Gemfile.lock') }} + restore-keys: | + gems-${{ runner.os }}-${{ env.ruby-version }}- + gems-${{ runner.os }}- + + - run: bundle config set deployment 'true' + - name: bundle install + run: | + bundle config path vendor/bundle + bundle install --jobs 4 --retry 3 + + - run: bundle exec middleman build + + - name: Deploy + uses: peaceiris/actions-gh-pages@v3 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_dir: ./build + keep_files: true diff --git a/.github/workflows/dev_deploy.yml b/.github/workflows/dev_deploy.yml new file mode 100644 index 00000000000..0373fe64411 --- /dev/null +++ b/.github/workflows/dev_deploy.yml @@ -0,0 +1,15 @@ +name: Dev Deploy + +on: + push: + branches: [ 'dev' ] + +jobs: + deploy: + # See https://github.com/oyindonesia/oybayar-docs/pull/119 about the reason of using 20.04 + # runs-on: ubuntu-22.04 + # env: + # ruby-version: 2.5 + runs-on: ubuntu-latest + env: + ruby-version: 2.6 \ No newline at end of file diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 00000000000..0d75acd4bac --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,24 @@ +name: Publish Docker image + +on: + release: + types: [published] + +jobs: + push_to_registry: + name: Push Docker image to Docker Hub + # See https://github.com/oyindonesia/oybayar-docs/pull/119 about the reason of using 20.04 + # runs-on: ubuntu-22.04 + runs-on: ubuntu-latest + steps: + - name: Check out the repo + uses: actions/checkout@v2 + + - name: Push to Docker Hub + uses: docker/build-push-action@v1 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_ACCESS_KEY }} + repository: slatedocs/slate + tag_with_ref: true + tags: latest diff --git a/.gitignore b/.gitignore index f6fc8c00b25..767632d9dd9 100644 --- a/.gitignore +++ b/.gitignore @@ -14,9 +14,15 @@ tmp *.DS_STORE build/ .cache +.vagrant +.sass-cache # YARD artifacts .yardoc _yardoc doc/ -.idea/ \ No newline at end of file +.idea/ +vendor/ + +# Vagrant artifacts +ubuntu-*-console.log diff --git a/.ruby-version b/.ruby-version new file mode 100644 index 00000000000..30f69e8cc57 --- /dev/null +++ b/.ruby-version @@ -0,0 +1 @@ +2.5.9 diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 3280d947c9a..00000000000 --- a/.travis.yml +++ /dev/null @@ -1,9 +0,0 @@ -sudo: false - -language: ruby - -rvm: - - 1.9.3 - - 2.0.0 - -cache: bundler diff --git a/CHANGELOG.md b/CHANGELOG.md index 59ee6a441e5..49b83dc3257 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,294 @@ # Changelog +## Version 2.13.1 + +*January 31, 2023* + +* Fix Vagrantfile gem install for ruby >= 2.6 (thanks @Cyb0rk) +* Disable file watcher in run_build() for sake of qemu on arm64 (thanks @anapsix) +* Expand deprecated git.io links to full url in docs (thanks @judge2020) +* Add margin to paragraph following code-block on phones (thanks @tlhunter) +* Bump nokogiri from 1.13.4 to 1.13.9 +* Bump rouge from 3.28.0 to 3.30.0 +* Bump redcarpet from 3.5.1 to 3.6.0 +* Bump middleman from 4.4.2 to 4.4.3 +* Bump middleman-syntax from 3.2.0 to 3.3.0 +* Bump webrick from 1.7.0 to 1.8.1 + +## Version 2.13.0 + +*April 22, 2022* + +* __Drop support for ruby 2.5__ +* Bump rouge from 3.26.1 to 3.28.0 +* Formally support ruby 3.1 +* Bump nokogiri from 1.12.5 to 1.13.4 +* Build docker images for multiple architectures (e.g. `aarch64`) +* Remove `VOLUME` declaration from Dockerfile (thanks @aemengo) + +The security vulnerabilities reported against recent versions of nokogiri should not affect slate users with a regular setup. + +## Version 2.12.0 + +*November 04, 2021* + +* Bump nokogiri from 1.12.3 to 1.12.5 +* Bump ffi from 1.15.0 to 1.15.4 +* Bump rouge from 3.26.0 to 3.26.1 +* Bump middleman from 4.4.0 to 4.4.2 +* Remove unnecessary files from docker images + +## Version 2.11.0 + +*August 12, 2021* + +* __[Security]__ Bump addressable transitive dependency from 2.7.0 to 2.8.0 +* Support specifying custom meta tags in YAML front-matter +* Bump nokogiri from 1.11.3 to 1.12.3 (minimum supported version is 1.11.4) +* Bump middleman-autoprefixer from 2.10.1 to 3.0.0 +* Bump jquery from 3.5.1 to 3.6.0 +* Bump middleman from [`d180ca3`](https://github.com/middleman/middleman/commit/d180ca337202873f2601310c74ba2b6b4cf063ec) to 4.4.0 + +## Version 2.10.0 + +*April 13, 2021* + +* Add support for Ruby 3.0 (thanks @shaun-scale) +* Add requirement for Git on installing dependencies +* Bump nokogiri from 1.11.2 to 1.11.3 +* Bump middleman from 4.3.11 to [`d180ca3`](https://github.com/middleman/middleman/commit/d180ca337202873f2601310c74ba2b6b4cf063ec) + +## Version 2.9.2 + +*March 30, 2021* + +* __[Security]__ Bump kramdown from 2.3.0 to 2.3.1 +* Bump nokogiri from 1.11.1 to 1.11.2 + +## Version 2.9.1 + +*February 27, 2021* + +* Fix Slate language tabs not working if localStorage is disabled + +## Version 2.9.0 + +*January 19, 2021* + +* __Drop support for Ruby 2.3 and 2.4__ +* __[Security]__ Bump nokogiri from 1.10.10 to 1.11.1 +* __[Security]__ Bump redcarpet from 3.5.0 to 3.5.1 +* Specify slate is not supported on Ruby 3.x +* Bump rouge from 3.24.0 to 3.26.0 + +## Version 2.8.0 + +*October 27, 2020* + +* Remove last trailing newline when using the copy code button +* Rework docker image and make available at slatedocs/slate +* Improve Dockerfile layout to improve caching (thanks @micvbang) +* Bump rouge from 3.20 to 3.24 +* Bump nokogiri from 1.10.9 to 1.10.10 +* Bump middleman from 4.3.8 to 4.3.11 +* Bump lunr.js from 2.3.8 to 2.3.9 + +## Version 2.7.1 + +*August 13, 2020* + +* __[security]__ Bumped middleman from 4.3.7 to 4.3.8 + +_Note_: Slate uses redcarpet, not kramdown, for rendering markdown to HTML, and so was unaffected by the security vulnerability in middleman. +If you have changed slate to use kramdown, and with GFM, you may need to install the `kramdown-parser-gfm` gem. + +## Version 2.7.0 + +*June 21, 2020* + +* __[security]__ Bumped rack in Gemfile.lock from 2.2.2 to 2.2.3 +* Bumped bundled jQuery from 3.2.1 to 3.5.1 +* Bumped bundled lunr from 0.5.7 to 2.3.8 +* Bumped imagesloaded from 3.1.8 to 4.1.4 +* Bumped rouge from 3.17.0 to 3.20.0 +* Bumped redcarpet from 3.4.0 to 3.5.0 +* Fix color of highlighted code being unreadable when printing page +* Add clipboard icon for "Copy to Clipboard" functionality to code boxes (see note below) +* Fix handling of ToC selectors that contain punctutation (thanks @gruis) +* Fix language bar truncating languages that overflow screen width +* Strip HTML tags from ToC title before displaying it in title bar in JS (backup to stripping done in Ruby code) (thanks @atic) + +To enable the new clipboard icon, you need to add `code_clipboard: true` to the frontmatter of source/index.html.md. +See [this line](https://github.com/slatedocs/slate/blame/main/source/index.html.md#L19) for an example of usage. + +## Version 2.6.1 + +*May 30, 2020* + +* __[security]__ update child dependency activesupport in Gemfile.lock to 5.4.2.3 +* Update Middleman in Gemfile.lock to 4.3.7 +* Replace Travis-CI with GitHub actions for continuous integration +* Replace Spectrum with GitHub discussions + +## Version 2.6.0 + +*May 18, 2020* + +__Note__: 2.5.0 was "pulled" due to a breaking bug discovered after release. It is recommended to skip it, and move straight to 2.6.0. + +* Fix large whitespace gap in middle column for sections with codeblocks +* Fix highlighted code elements having a different background than rest of code block +* Change JSON keys to have a different font color than their values +* Disable asset hashing for woff and woff2 elements due to middleman bug breaking woff2 asset hashing in general +* Move Dockerfile to Debian from Alpine +* Converted repo to a [GitHub template](https://help.github.com/en/github/creating-cloning-and-archiving-repositories/creating-a-template-repository) +* Update sassc to 2.3.0 in Gemfile.lock + +## Version 2.5.0 + +*May 8, 2020* + +* __[security]__ update nokogiri to ~> 1.10.8 +* Update links in example docs to https://github.com/slatedocs/slate from https://github.com/lord/slate +* Update LICENSE to include full Apache 2.0 text +* Test slate against Ruby 2.5 and 2.6 on Travis-CI +* Update Vagrantfile to use Ubuntu 18.04 (thanks @bradthurber) +* Parse arguments and flags for deploy.sh on script start, instead of potentially after building source files +* Install nodejs inside Vagrantfile (thanks @fernandoaguilar) +* Add Dockerfile for running slate (thanks @redhatxl) +* update middleman-syntax and rouge to ~>3.2 +* update middleman to 4.3.6 + +## Version 2.4.0 + +*October 19, 2019* + +- Move repository from lord/slate to slatedocs/slate +- Fix documentation to point at new repo link, thanks to [Arun](https://github.com/slash-arun), [Gustavo Gawryszewski](https://github.com/gawry), and [Daniel Korbit](https://github.com/danielkorbit) +- Update `nokogiri` to 1.10.4 +- Update `ffi` in `Gemfile.lock` to fix security warnings, thanks to [Grey Baker](https://github.com/greysteil) and [jakemack](https://github.com/jakemack) +- Update `rack` to 2.0.7 in `Gemfile.lock` to fix security warnings, thanks to [Grey Baker](https://github.com/greysteil) and [jakemack](https://github.com/jakemack) +- Update middleman to `4.3` and relax constraints on middleman related gems, thanks to [jakemack](https://github.com/jakemack) +- Add sass gem, thanks to [jakemack](https://github.com/jakemack) +- Activate `asset_cache` in middleman to improve cacheability of static files, thanks to [Sam Gilman](https://github.com/thenengah) +- Update to using bundler 2 for `Gemfile.lock`, thanks to [jakemack](https://github.com/jakemack) + +## Version 2.3.1 + +*July 5, 2018* + +- Update `sprockets` in `Gemfile.lock` to fix security warnings + +## Version 2.3 + +*July 5, 2018* + +- Allows strikethrough in markdown by default. +- Upgrades jQuery to 3.2.1, thanks to [Tomi Takussaari](https://github.com/TomiTakussaari) +- Fixes invalid HTML in `layout.erb`, thanks to [Eric Scouten](https://github.com/scouten) for pointing out +- Hopefully fixes Vagrant memory issues, thanks to [Petter Blomberg](https://github.com/p-blomberg) for the suggestion +- Cleans HTML in headers before setting `document.title`, thanks to [Dan Levy](https://github.com/justsml) +- Allows trailing whitespace in markdown files, thanks to [Samuel Cousin](https://github.com/kuzyn) +- Fixes pushState/replaceState problems with scrolling not changing the document hash, thanks to [Andrey Fedorov](https://github.com/anfedorov) +- Removes some outdated examples, thanks [@al-tr](https://github.com/al-tr), [Jerome Dahdah](https://github.com/jdahdah), and [Ricardo Castro](https://github.com/mccricardo) +- Fixes `nav-padding` bug, thanks [Jerome Dahdah](https://github.com/jdahdah) +- Code style fixes thanks to [Sebastian Zaremba](https://github.com/vassyz) +- Nokogiri version bump thanks to [Grey Baker](https://github.com/greysteil) +- Fix to default `index.md` text thanks to [Nick Busey](https://github.com/NickBusey) + +Thanks to everyone who contributed to this release! + +## Version 2.2 + +*January 19, 2018* + +- Fixes bugs with some non-roman languages not generating unique headers +- Adds editorconfig, thanks to [Jay Thomas](https://github.com/jaythomas) +- Adds optional `NestingUniqueHeadCounter`, thanks to [Vladimir Morozov](https://github.com/greenhost87) +- Small fixes to typos and language, thx [Emir Ribić](https://github.com/ribice), [Gregor Martynus](https://github.com/gr2m), and [Martius](https://github.com/martiuslim)! +- Adds links to Spectrum chat for questions in README and ISSUE_TEMPLATE + +## Version 2.1 + +*October 30, 2017* + +- Right-to-left text stylesheet option, thanks to [Mohammad Hossein Rabiee](https://github.com/mhrabiee) +- Fix for HTML5 history state bug, thanks to [Zach Toolson](https://github.com/ztoolson) +- Small styling changes, typo fixes, small bug fixes from [Marian Friedmann](https://github.com/rnarian), [Ben Wilhelm](https://github.com/benwilhelm), [Fouad Matin](https://github.com/fouad), [Nicolas Bonduel](https://github.com/NicolasBonduel), [Christian Oliff](https://github.com/coliff) + +Thanks to everyone who submitted PRs for this version! + +## Version 2.0 + +*July 17, 2017* + +- All-new statically generated table of contents + - Should be much faster loading and scrolling for large pages + - Smaller Javascript file sizes + - Avoids the problem with the last link in the ToC not ever highlighting if the section was shorter than the page + - Fixes control-click not opening in a new page + - Automatically updates the HTML title as you scroll +- Updated design + - New default colors! + - New spacings and sizes! + - System-default typefaces, just like GitHub +- Added search input delay on large corpuses to reduce lag +- We even bumped the major version cause hey, why not? +- Various small bug fixes + +Thanks to everyone who helped debug or wrote code for this version! It was a serious community effort, and I couldn't have done it alone. + +## Version 1.5 + +*February 23, 2017* + +- Add [multiple tabs per programming language](https://github.com/lord/slate/wiki/Multiple-language-tabs-per-programming-language) feature +- Upgrade Middleman to add Ruby 1.4.0 compatibility +- Switch default code highlighting color scheme to better highlight JSON +- Various small typo and bug fixes + +## Version 1.4 + +*November 24, 2016* + +- Upgrade Middleman and Rouge gems, should hopefully solve a number of bugs +- Update some links in README +- Fix broken Vagrant startup script +- Fix some problems with deploy.sh help message +- Fix bug with language tabs not hiding properly if no error +- Add `!default` to SASS variables +- Fix bug with logo margin +- Bump tested Ruby versions in .travis.yml + +## Version 1.3.3 + +*June 11, 2016* + +Documentation and example changes. + +## Version 1.3.2 + +*February 3, 2016* + +A small bugfix for slightly incorrect background colors on code samples in some cases. + +## Version 1.3.1 + +*January 31, 2016* + +A small bugfix for incorrect whitespace in code blocks. + +## Version 1.3 + +*January 27, 2016* + +We've upgraded Middleman and a number of other dependencies, which should fix quite a few bugs. + +Instead of `rake build` and `rake deploy`, you should now run `bundle exec middleman build --clean` to build your server, and `./deploy.sh` to deploy it to Github Pages. + ## Version 1.2 -*June 20, 2014* +*June 20, 2015* **Fixes:** @@ -21,7 +307,7 @@ ## Version 1.1 -*July 27th, 2014* +*July 27, 2014* **Fixes:** diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 00000000000..cc17fd98d59 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,46 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at hello@lord.io. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] + +[homepage]: http://contributor-covenant.org +[version]: http://contributor-covenant.org/version/1/4/ diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md deleted file mode 100644 index b04fc955ca5..00000000000 --- a/CONTRIBUTING.md +++ /dev/null @@ -1,9 +0,0 @@ -# Contributing to Slate - -Thanks for contributing to Slate! A couple of quick guidelines for submitting PRs: - -- Please point your pull requests at the `dev` branch, and keep your commit messages clear and informative. -- Please make sure your contributions work in the most recent version of Chrome, Firefox, and IE. -- If you're implementing a new feature, even if it's relatively small, it's nice to open an issue before you start so that others know what you're working on and can help make sure you're on the right track. - -Thanks again! Happy coding. \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 8183c7a8b04..c23655e7d7c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,12 +1,26 @@ -FROM ubuntu:trusty - -RUN apt-get update -RUN apt-get install -yq ruby ruby-dev build-essential git -RUN gem install --no-ri --no-rdoc bundler -ADD Gemfile /app/Gemfile -ADD Gemfile.lock /app/Gemfile.lock -RUN cd /app; bundle install -ADD . /app +FROM ruby:2.6-slim + +WORKDIR /srv/slate + EXPOSE 4567 -WORKDIR /app -CMD ["bundle", "exec", "middleman", "server"] + +COPY Gemfile . +COPY Gemfile.lock . + +RUN apt-get update \ + && apt-get install -y --no-install-recommends \ + build-essential \ + git \ + nodejs \ + && gem install bundler -v 2.4.22 \ + && bundle install \ + && apt-get remove -y build-essential git \ + && apt-get autoremove -y \ + && rm -rf /var/lib/apt/lists/* + +COPY . /srv/slate + +RUN chmod +x /srv/slate/slate.sh + +ENTRYPOINT ["/srv/slate/slate.sh"] +CMD ["build"] diff --git a/Gemfile b/Gemfile index 3a2a2e01a82..29476c47651 100644 --- a/Gemfile +++ b/Gemfile @@ -1,12 +1,14 @@ +ruby '>= 2.6' source 'https://rubygems.org' # Middleman -gem 'middleman', '~>3.3.10' -gem 'middleman-gh-pages', '~> 0.0.3' -gem 'middleman-syntax', '~> 2.0.0' -gem 'middleman-autoprefixer', '~> 2.4.4' -gem 'rouge', '~> 1.9.0' -gem 'redcarpet', '~> 3.3.1' - -gem 'rake', '~> 10.4.2' -gem 'therubyracer', '~> 0.12.1', platforms: :ruby +gem 'middleman', '~> 4.4' +gem 'middleman-syntax', '~> 3.2' +gem 'middleman-autoprefixer', '~> 3.0' +gem 'middleman-sprockets', '~> 4.1' +gem 'middleman-livereload', '~> 3.4' +gem 'rouge', '~> 3.21' +gem 'redcarpet', '~> 3.6.0' +gem 'nokogiri', '~> 1.13.3' +gem 'sass' +gem 'webrick' diff --git a/Gemfile.lock b/Gemfile.lock index f9978492816..7fd7d918b53 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,140 +1,157 @@ GEM remote: https://rubygems.org/ specs: - activesupport (4.1.11) - i18n (~> 0.6, >= 0.6.9) - json (~> 1.7, >= 1.7.7) - minitest (~> 5.1) - thread_safe (~> 0.1) - tzinfo (~> 1.1) - autoprefixer-rails (5.2.0.1) - execjs - json - celluloid (0.16.0) - timers (~> 4.0.0) - chunky_png (1.3.4) + activesupport (6.1.6.1) + concurrent-ruby (~> 1.0, >= 1.0.2) + i18n (>= 1.6, < 2) + minitest (>= 5.1) + tzinfo (~> 2.0) + zeitwerk (~> 2.3) + addressable (2.8.1) + public_suffix (>= 2.0.2, < 6.0) + autoprefixer-rails (10.2.5.0) + execjs (< 2.8.0) + backports (3.23.0) coffee-script (2.4.1) coffee-script-source execjs - coffee-script-source (1.9.1.1) - compass (1.0.3) - chunky_png (~> 1.2) - compass-core (~> 1.0.2) - compass-import-once (~> 1.0.5) - rb-fsevent (>= 0.9.3) - rb-inotify (>= 0.9) - sass (>= 3.3.13, < 3.5) - compass-core (1.0.3) - multi_json (~> 1.0) - sass (>= 3.3.0, < 3.5) - compass-import-once (1.0.5) - sass (>= 3.2, < 3.5) + coffee-script-source (1.12.2) + concurrent-ruby (1.2.0) + contracts (0.16.1) + dotenv (2.8.1) + em-websocket (0.5.3) + eventmachine (>= 0.12.9) + http_parser.rb (~> 0) erubis (2.7.0) - execjs (2.5.2) - ffi (1.9.8) - haml (4.0.6) + eventmachine (1.2.7) + execjs (2.7.0) + fast_blank (1.0.1) + fastimage (2.2.6) + ffi (1.15.5) + haml (5.2.2) + temple (>= 0.8.0) tilt - hike (1.2.3) - hitimes (1.2.2) - hooks (0.4.0) - uber (~> 0.0.4) - i18n (0.7.0) - json (1.8.3) - kramdown (1.7.0) - libv8 (3.16.14.7) - listen (2.10.1) - celluloid (~> 0.16.0) - rb-fsevent (>= 0.9.3) - rb-inotify (>= 0.9) - middleman (3.3.12) + hamster (3.0.0) + concurrent-ruby (~> 1.0) + hashie (3.6.0) + http_parser.rb (0.8.0) + i18n (1.6.0) + concurrent-ruby (~> 1.0) + kramdown (2.4.0) + rexml + listen (3.8.0) + rb-fsevent (~> 0.10, >= 0.10.3) + rb-inotify (~> 0.9, >= 0.9.10) + memoist (0.16.2) + middleman (4.4.3) coffee-script (~> 2.2) - compass (>= 1.0.0, < 2.0.0) - compass-import-once (= 1.0.5) - execjs (~> 2.0) - haml (>= 4.0.5) - kramdown (~> 1.2) - middleman-core (= 3.3.12) - middleman-sprockets (>= 3.1.2) - sass (>= 3.4.0, < 4.0) - uglifier (~> 2.5) - middleman-autoprefixer (2.4.4) - autoprefixer-rails (~> 5.2.0) - middleman-core (>= 3.3.3) - middleman-core (3.3.12) - activesupport (~> 4.1.0) - bundler (~> 1.1) + haml (>= 4.0.5, < 6.0) + kramdown (>= 2.3.0) + middleman-cli (= 4.4.3) + middleman-core (= 4.4.3) + middleman-autoprefixer (3.0.0) + autoprefixer-rails (~> 10.0) + middleman-core (>= 4.0.0) + middleman-cli (4.4.3) + thor (>= 0.17.0, < 2.0) + middleman-core (4.4.3) + activesupport (>= 6.1, < 7.1) + addressable (~> 2.4) + backports (~> 3.6) + bundler (~> 2.0) + contracts (~> 0.13) + dotenv erubis - hooks (~> 0.3) - i18n (~> 0.7.0) - listen (>= 2.7.9, < 3.0) - padrino-helpers (~> 0.12.3) - rack (>= 1.4.5, < 2.0) - rack-test (~> 0.6.2) - thor (>= 0.15.2, < 2.0) - tilt (~> 1.4.1, < 2.0) - middleman-gh-pages (0.0.3) - rake (> 0.9.3) - middleman-sprockets (3.4.2) + execjs (~> 2.0) + fast_blank + fastimage (~> 2.0) + hamster (~> 3.0) + hashie (~> 3.4) + i18n (~> 1.6.0) + listen (~> 3.0) + memoist (~> 0.14) + padrino-helpers (~> 0.15.0) + parallel + rack (>= 1.4.5, < 3) + sassc (~> 2.0) + servolux + tilt (~> 2.0.9) + toml + uglifier (~> 3.0) + webrick + middleman-livereload (3.4.7) + em-websocket (~> 0.5.1) middleman-core (>= 3.3) - sprockets (~> 2.12.1) - sprockets-helpers (~> 1.1.0) - sprockets-sass (~> 1.3.0) - middleman-syntax (2.0.0) - middleman-core (~> 3.2) - rouge (~> 1.0) - minitest (5.7.0) - multi_json (1.11.1) - padrino-helpers (0.12.5) - i18n (~> 0.6, >= 0.6.7) - padrino-support (= 0.12.5) - tilt (~> 1.4.1) - padrino-support (0.12.5) - activesupport (>= 3.1) - rack (1.6.4) - rack-test (0.6.3) - rack (>= 1.0) - rake (10.4.2) - rb-fsevent (0.9.5) - rb-inotify (0.9.5) - ffi (>= 0.5.0) - redcarpet (3.3.1) - ref (1.0.5) - rouge (1.9.0) - sass (3.4.14) - sprockets (2.12.3) - hike (~> 1.2) - multi_json (~> 1.0) - rack (~> 1.0) - tilt (~> 1.1, != 1.3.0) - sprockets-helpers (1.1.0) - sprockets (~> 2.0) - sprockets-sass (1.3.1) - sprockets (~> 2.0) - tilt (~> 1.1) - therubyracer (0.12.2) - libv8 (~> 3.16.14.0) - ref - thor (0.19.1) - thread_safe (0.3.5) - tilt (1.4.1) - timers (4.0.1) - hitimes - tzinfo (1.2.2) - thread_safe (~> 0.1) - uber (0.0.13) - uglifier (2.7.1) - execjs (>= 0.3.0) - json (>= 1.8.0) + rack-livereload (~> 0.3.15) + middleman-sprockets (4.1.1) + middleman-core (~> 4.0) + sprockets (>= 3.0) + middleman-syntax (3.3.0) + middleman-core (>= 3.2) + rouge (~> 3.2) + mini_portile2 (2.8.0) + minitest (5.17.0) + nokogiri (1.13.9) + mini_portile2 (~> 2.8.0) + racc (~> 1.4) + padrino-helpers (0.15.2) + i18n (>= 0.6.7, < 2) + padrino-support (= 0.15.2) + tilt (>= 1.4.1, < 3) + padrino-support (0.15.2) + parallel (1.22.1) + parslet (2.0.0) + public_suffix (5.0.1) + racc (1.6.0) + rack (2.2.6.2) + rack-livereload (0.3.17) + rack + rb-fsevent (0.11.2) + rb-inotify (0.10.1) + ffi (~> 1.0) + redcarpet (3.6.0) + rexml (3.2.5) + rouge (3.30.0) + sass (3.7.4) + sass-listen (~> 4.0.0) + sass-listen (4.0.0) + rb-fsevent (~> 0.9, >= 0.9.4) + rb-inotify (~> 0.9, >= 0.9.7) + sassc (2.4.0) + ffi (~> 1.9) + servolux (0.13.0) + sprockets (3.7.2) + concurrent-ruby (~> 1.0) + rack (> 1, < 3) + temple (0.10.0) + thor (1.2.1) + tilt (2.0.11) + toml (0.3.0) + parslet (>= 1.8.0, < 3.0.0) + tzinfo (2.0.6) + concurrent-ruby (~> 1.0) + uglifier (3.2.0) + execjs (>= 0.3.0, < 3) + webrick (1.8.1) + zeitwerk (2.6.0) PLATFORMS ruby DEPENDENCIES - middleman (~> 3.3.10) - middleman-autoprefixer (~> 2.4.4) - middleman-gh-pages (~> 0.0.3) - middleman-syntax (~> 2.0.0) - rake (~> 10.4.2) - redcarpet (~> 3.3.1) - rouge (~> 1.9.0) - therubyracer (~> 0.12.1) + middleman (~> 4.4) + middleman-autoprefixer (~> 3.0) + middleman-livereload (~> 3.4) + middleman-sprockets (~> 4.1) + middleman-syntax (~> 3.2) + nokogiri (~> 1.13.3) + redcarpet (~> 3.6.0) + rouge (~> 3.21) + sass + webrick + +RUBY VERSION + ruby 2.7.2p137 + +BUNDLED WITH + 2.2.22 diff --git a/LICENSE b/LICENSE index 5ceddf59f68..261eeb9e9f8 100644 --- a/LICENSE +++ b/LICENSE @@ -1,13 +1,201 @@ -Copyright 2008-2013 Concur Technologies, Inc. + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ -Licensed under the Apache License, Version 2.0 (the "License"); you may -not use this file except in compliance with the License. You may obtain -a copy of the License at + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - http://www.apache.org/licenses/LICENSE-2.0 + 1. Definitions. -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -License for the specific language governing permissions and limitations -under the License. \ No newline at end of file + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README.md b/README.md index efb7e1eb8cd..3e2648bb3ff 100644 --- a/README.md +++ b/README.md @@ -1,122 +1,98 @@ -Slate -======== +

+ Slate: API Documentation Generator +
+ Build Status + Docker Version +

-[![Build Status](https://travis-ci.org/tripit/slate.svg?branch=master)](https://travis-ci.org/tripit/slate) [![Dependency Status](https://gemnasium.com/tripit/slate.png)](https://gemnasium.com/tripit/slate) +

Slate helps you create beautiful, intelligent, responsive API documentation.

-Slate helps you create beautiful API documentation. Think of it as an intelligent, responsive documentation template for your API. +

Screenshot of Example Documentation created with Slate

-Screenshot of Example Documentation created with Slate +

The example above was created with Slate. Check it out at slatedocs.github.io/slate.

-*The example above was created with Slate. Check it out at [tripit.github.io/slate](http://tripit.github.io/slate).* +**Prerequisite** +1. Install docker +`brew install docker` +2. Install docker compose +`brew install docker-compose` -Features +**How to Run in Local** +------------ +1. Build the Docker image: +`docker-compose build` +2. Start the container: +`docker-compose up -d` +3. To check the container logs: +`docker-compose logs -f` +4. Open the application in your web browser by visiting `http://localhost:4567/` + +**Features** ------------ -* **Clean, intuitive design** — with Slate, the description of your API is on the left side of your documentation, and all the code examples are on the right side. Inspired by [Stripe's](https://stripe.com/docs/api) and [Paypal's](https://developer.paypal.com/webapps/developer/docs/api/) API docs. Slate is responsive, so it looks great on tablets, phones, and even print. +* **Clean, intuitive design** — With Slate, the description of your API is on the left side of your documentation, and all the code examples are on the right side. Inspired by [Stripe's](https://stripe.com/docs/api) and [PayPal's](https://developer.paypal.com/webapps/developer/docs/api/) API docs. Slate is responsive, so it looks great on tablets, phones, and even in print. -* **Everything on a single page** — gone are the days where your users had to search through a million pages to find what they wanted. Slate puts the entire documentation on a single page. We haven't sacrificed linkability, though. As you scroll, your browser's hash will update to the nearest header, so linking to a particular point in the documentation is still natural and easy. +* **Everything on a single page** — Gone are the days when your users had to search through a million pages to find what they wanted. Slate puts the entire documentation on a single page. We haven't sacrificed linkability, though. As you scroll, your browser's hash will update to the nearest header, so linking to a particular point in the documentation is still natural and easy. -* **Slate is just Markdown** — when you write docs with Slate, you're just writing Markdown, which makes it simple to edit and understand. Everything is written in Markdown — even the code samples are just Markdown code blocks! +* **Slate is just Markdown** — When you write docs with Slate, you're just writing Markdown, which makes it simple to edit and understand. Everything is written in Markdown — even the code samples are just Markdown code blocks. -* **Write code samples in multiple languages** — if your API has bindings in multiple programming languages, you easily put in tabs to switch between them. In your document, you'll distinguish different languages by specifying the language name at the top of each code block, just like with Github Flavored Markdown! +* **Write code samples in multiple languages** — If your API has bindings in multiple programming languages, you can easily put in tabs to switch between them. In your document, you'll distinguish different languages by specifying the language name at the top of each code block, just like with GitHub Flavored Markdown. -* **Out-of-the-box syntax highlighting** for [almost 60 languages](http://rouge.jayferd.us/demo), no configuration required. +* **Out-of-the-box syntax highlighting** for [over 100 languages](https://github.com/rouge-ruby/rouge/wiki/List-of-supported-languages-and-lexers), no configuration required. * **Automatic, smoothly scrolling table of contents** on the far left of the page. As you scroll, it displays your current position in the document. It's fast, too. We're using Slate at TripIt to build documentation for our new API, where our table of contents has over 180 entries. We've made sure that the performance remains excellent, even for larger documents. -* **Let your users update your documentation for you** — by default, your Slate-generated documentation is hosted in a public Github repository. Not only does this mean you get free hosting for your docs with Github Pages, but it also makes it's simple for other developers to make pull requests to your docs if they find typos or other problems. Of course, if you don't want to, you're welcome to not use Github and host your docs elsewhere! +* **Let your users update your documentation for you** — By default, your Slate-generated documentation is hosted in a public GitHub repository. Not only does this mean you get free hosting for your docs with GitHub Pages, but it also makes it simple for other developers to make pull requests to your docs if they find typos or other problems. Of course, if you don't want to use GitHub, you're also welcome to host your docs elsewhere. -Getting starting with Slate is super easy! Simply fork this repository, and then follow the instructions below. Or, if you'd like to check out what Slate is capable of, take a look at the [sample docs](http://tripit.github.io/slate). +* **RTL Support** Full right-to-left layout for RTL languages such as Arabic, Persian (Farsi), Hebrew etc. - +Getting started with Slate is super easy! Simply press the green "use this template" button above and follow the instructions below. Or, if you'd like to check out what Slate is capable of, take a look at the [sample docs](https://slatedocs.github.io/slate/). -Getting Started with Slate +**Getting Started with Slate** ------------------------------ -### Prerequisites - -You're going to need: - - - **Linux or OS X** — Windows may work, but is unsupported. - - **Ruby, version 1.9.3 or newer** - - **Bundler** — If Ruby is already installed, but the `bundle` command doesn't work, just run `gem install bundler` in a terminal. - -### Getting Set Up - - 1. Fork this repository on Github. - 2. Clone *your forked repository* (not our original one) to your hard drive with `git clone https://github.com/YOURUSERNAME/slate.git` - 3. `cd slate` - 4. Install all dependencies: `bundle install` - 5. Start the test server: `bundle exec middleman server` - -Or use the included Dockerfile! (must install Docker first) +To get started with Slate, please check out the [Getting Started](https://github.com/slatedocs/slate/wiki#getting-started) +section in our [wiki](https://github.com/slatedocs/slate/wiki). -```shell -docker build -t slate . -docker run -d -p 4567:4567 slate -``` +We support running Slate in three different ways: +* [Natively](https://github.com/slatedocs/slate/wiki/Using-Slate-Natively) +* [Using Vagrant](https://github.com/slatedocs/slate/wiki/Using-Slate-in-Vagrant) +* [Using Docker](https://github.com/slatedocs/slate/wiki/Using-Slate-in-Docker) -You can now see the docs at . Whoa! That was fast! - -*Note: if you're using the Docker setup on OSX, the docs will be -availalable at the output of `boot2docker ip` instead of `localhost:4567`.* - -Now that Slate is all set up your machine, you'll probably want to learn more about [editing Slate markdown](https://github.com/tripit/slate/wiki/Markdown-Syntax), or [how to publish your docs](https://github.com/tripit/slate/wiki/Deploying-Slate). - -Examples of Slate in the Wild +**Companies Using Slate** --------------------------------- -* [Travis-CI's API docs](http://docs.travis-ci.com/api/) -* [Mozilla's localForage docs](http://mozilla.github.io/localForage/) -* [Mozilla Recroom](http://mozilla.github.io/recroom/) -* [ChaiOne Gameplan API docs](http://chaione.github.io/gameplanb2b/#introduction) -* [Drcaban's Build a Quine tutorial](http://drcabana.github.io/build-a-quine/#introduction) -* [PricePlow API docs](https://www.priceplow.com/api/documentation) -* [Emerging Threats API docs](http://apidocs.emergingthreats.net/) -* [Appium docs](http://appium.io/slate/en/master) -* [Golazon Developer](http://developer.golazon.com) -* [Dwolla API docs](https://docs.dwolla.com/) -* [RozpisyZapasu API docs](http://www.rozpisyzapasu.cz/dev/api/) -* [Codestar Framework Docs](http://codestarframework.com/documentation/) -* [Buddycloud API](http://buddycloud.com/api) -* [Crafty Clicks API](https://craftyclicks.co.uk/api/) -* [Paracel API Reference](http://paracel.io/docs/api_reference.html) -* [Switch Payments Documentation](http://switchpayments.com/docs/) & [API](http://switchpayments.com/developers/) -* [Coinbase API Reference](https://developers.coinbase.com/api) -* [Whispir.io API](https://whispir.github.io/api) -* [NASA API](https://data.nasa.gov/developer/external/planetary/) -* [CardPay API](https://developers.cardpay.com/) -* [IBM Cloudant](https://docs-testb.cloudant.com/content-review/_design/couchapp/index.html) -* [Bitrix basis components](http://bbc.bitrix.expert/) -* [viagogo API Documentation](http://developer.viagogo.net/) -* [Fidor Bank API Documentation](http://docs.fidor.de/) -* [Market Prophit API Documentation](http://developer.marketprophit.com/) - -(Feel free to add your site to this list in a pull request!) - -Need Help? Found a bug? +* [NASA](https://api.nasa.gov) +* [Sony](http://developers.cimediacloud.com) +* [Best Buy](https://bestbuyapis.github.io/api-documentation/) +* [Travis-CI](https://docs.travis-ci.com/api/) +* [Greenhouse](https://developers.greenhouse.io/harvest.html) +* [WooCommerce](http://woocommerce.github.io/woocommerce-rest-api-docs/) +* [Dwolla](https://docs.dwolla.com/) +* [Clearbit](https://clearbit.com/docs) +* [Coinbase](https://developers.coinbase.com/api) +* [Parrot Drones](http://developer.parrot.com/docs/bebop/) +* [CoinAPI](https://docs.coinapi.io/) + +You can view more in [the list on the wiki](https://github.com/slatedocs/slate/wiki/Slate-in-the-Wild). + +**Questions? Need Help? Found a bug?** -------------------- -Just [submit a issue](https://github.com/tripit/slate/issues) to the Slate Github if you need any help. And, of course, feel free to submit pull requests with bug fixes or changes. +If you've got questions about setup, deploying, special feature implementation in your fork, or just want to chat with the developer, please feel free to [start a thread in our Discussions tab](https://github.com/slatedocs/slate/discussions)! +Found a bug with upstream Slate? Go ahead and [submit an issue](https://github.com/slatedocs/slate/issues). And, of course, feel free to submit pull requests with bug fixes or changes to the `dev` branch. -Contributors +**Contributors** -------------------- -Slate was built by [Robert Lord](https://lord.io) while at [TripIt](http://tripit.com). +Slate was built by [Robert Lord](https://lord.io) while at [TripIt](https://www.tripit.com/). The project is now maintained by [Matthew Peveler](https://github.com/MasterOdin) and [Mike Ralphson](https://github.com/MikeRalphson). Thanks to the following people who have submitted major pull requests: - [@chrissrogers](https://github.com/chrissrogers) - [@bootstraponline](https://github.com/bootstraponline) - [@realityking](https://github.com/realityking) +- [@cvkef](https://github.com/cvkef) -Also, thanks to [Sauce Labs](http://saucelabs.com) for helping sponsor the project. - -Special Thanks --------------------- -- [Middleman](https://github.com/middleman/middleman) -- [jquery.tocify.js](https://github.com/gfranko/jquery.tocify.js) -- [middleman-syntax](https://github.com/middleman/middleman-syntax) -- [middleman-gh-pages](https://github.com/neo/middleman-gh-pages) -- [Font Awesome](http://fortawesome.github.io/Font-Awesome/) +Also, thanks to [Sauce Labs](http://saucelabs.com) for sponsoring the development of the responsive styles. diff --git a/Rakefile b/Rakefile deleted file mode 100644 index 6a952e1e914..00000000000 --- a/Rakefile +++ /dev/null @@ -1,6 +0,0 @@ -require 'middleman-gh-pages' -require 'rake/clean' - -CLOBBER.include('build') - -task :default => [:build] diff --git a/Vagrantfile b/Vagrantfile new file mode 100644 index 00000000000..8a9981b8f77 --- /dev/null +++ b/Vagrantfile @@ -0,0 +1,47 @@ +Vagrant.configure(2) do |config| + config.vm.box = "ubuntu/focal64" + config.vm.network :forwarded_port, guest: 4567, host: 4567 + config.vm.provider "virtualbox" do |vb| + vb.memory = "2048" + end + + config.vm.provision "bootstrap", + type: "shell", + inline: <<-SHELL + # add nodejs v12 repository + curl -sL https://deb.nodesource.com/setup_12.x | sudo -E bash - + + sudo apt-get update + sudo apt-get install -yq ruby ruby-dev + sudo apt-get install -yq pkg-config build-essential nodejs git libxml2-dev libxslt-dev + sudo apt-get autoremove -yq + gem install --no-document bundler + SHELL + + # add the local user git config to the vm + config.vm.provision "file", source: "~/.gitconfig", destination: ".gitconfig" + + config.vm.provision "install", + type: "shell", + privileged: false, + inline: <<-SHELL + echo "==============================================" + echo "Installing app dependencies" + cd /vagrant + sudo gem install bundler -v "$(grep -A 1 "BUNDLED WITH" Gemfile.lock | tail -n 1)" + bundle config build.nokogiri --use-system-libraries + bundle install + SHELL + + config.vm.provision "run", + type: "shell", + privileged: false, + run: "always", + inline: <<-SHELL + echo "==============================================" + echo "Starting up middleman at http://localhost:4567" + echo "If it does not come up, check the ~/middleman.log file for any error messages" + cd /vagrant + bundle exec middleman server --watcher-force-polling --watcher-latency=1 &> ~/middleman.log & + SHELL +end diff --git a/config.rb b/config.rb index 43bceaa5a43..aefc11f0697 100644 --- a/config.rb +++ b/config.rb @@ -1,3 +1,9 @@ +# Unique header generation +require './lib/oy_markdown_renderer.rb' + +activate :i18n, :mount_at_root => :en, :langs => [:id, :en] +activate :livereload + # Markdown set :markdown_engine, :redcarpet set :markdown, @@ -5,9 +11,11 @@ smartypants: true, disable_indented_code_blocks: true, prettify: true, + strikethrough: true, tables: true, with_toc_data: true, - no_intra_emphasis: true + no_intra_emphasis: true, + renderer: OyMarkdownRenderer # Assets set :css_dir, 'stylesheets' @@ -17,6 +25,12 @@ # Activate the syntax highlighter activate :syntax +ready do + require './lib/oy_theme.rb' + require './lib/multilang.rb' +end + +activate :sprockets activate :autoprefixer do |config| config.browsers = ['last 2 version', 'Firefox ESR'] @@ -30,9 +44,23 @@ # Build Configuration configure :build do + # We do want to hash woff and woff2 as there's a bug where woff2 will use + # woff asset hash which breaks things. Trying to use a combination of ignore and + # rewrite_ignore does not work as it conflicts weirdly with relative_assets. Disabling + # the .woff2 extension only does not work as .woff will still activate it so have to + # have both. See https://github.com/slatedocs/slate/issues/1171 for more details. + activate :asset_hash, :exts => app.config[:asset_extensions] - %w[.woff .woff2] + # If you're having trouble with Middleman hanging, commenting + # out the following two lines has been known to help activate :minify_css activate :minify_javascript - # activate :relative_assets - # activate :asset_hash # activate :gzip end + +# Deploy Configuration +# If you want Middleman to listen on a different port, you can set that below +set :port, 4567 + +helpers do + require './lib/toc_data.rb' +end diff --git a/deploy.sh b/deploy.sh new file mode 100755 index 00000000000..9dbd7db9c72 --- /dev/null +++ b/deploy.sh @@ -0,0 +1,226 @@ +#!/usr/bin/env bash +set -o errexit #abort if any command fails +me=$(basename "$0") + +help_message="\ +Usage: $me [-c FILE] [] +Deploy generated files to a git branch. + +Options: + + -h, --help Show this help information. + -v, --verbose Increase verbosity. Useful for debugging. + -e, --allow-empty Allow deployment of an empty directory. + -m, --message MESSAGE Specify the message used when committing on the + deploy branch. + -n, --no-hash Don't append the source commit's hash to the deploy + commit's message. + --source-only Only build but not push + --push-only Only push but not build +" + + +run_build() { + bundle exec middleman build --clean +} + +parse_args() { + # Set args from a local environment file. + if [ -e ".env" ]; then + source .env + fi + + # Parse arg flags + # If something is exposed as an environment variable, set/overwrite it + # here. Otherwise, set/overwrite the internal variable instead. + while : ; do + if [[ $1 = "-h" || $1 = "--help" ]]; then + echo "$help_message" + exit 0 + elif [[ $1 = "-v" || $1 = "--verbose" ]]; then + verbose=true + shift + elif [[ $1 = "-e" || $1 = "--allow-empty" ]]; then + allow_empty=true + shift + elif [[ ( $1 = "-m" || $1 = "--message" ) && -n $2 ]]; then + commit_message=$2 + shift 2 + elif [[ $1 = "-n" || $1 = "--no-hash" ]]; then + GIT_DEPLOY_APPEND_HASH=false + shift + elif [[ $1 = "--source-only" ]]; then + source_only=true + shift + elif [[ $1 = "--push-only" ]]; then + push_only=true + shift + else + break + fi + done + + if [ ${source_only} ] && [ ${push_only} ]; then + >&2 echo "You can only specify one of --source-only or --push-only" + exit 1 + fi + + # Set internal option vars from the environment and arg flags. All internal + # vars should be declared here, with sane defaults if applicable. + + # Source directory & target branch. + deploy_directory=build + deploy_branch=gh-pages + + #if no user identity is already set in the current git environment, use this: + default_username=${GIT_DEPLOY_USERNAME:-deploy.sh} + default_email=${GIT_DEPLOY_EMAIL:-} + + #repository to deploy to. must be readable and writable. + repo=origin + + #append commit hash to the end of message by default + append_hash=${GIT_DEPLOY_APPEND_HASH:-true} +} + +main() { + enable_expanded_output + + if ! git diff --exit-code --quiet --cached; then + echo Aborting due to uncommitted changes in the index >&2 + return 1 + fi + + commit_title=`git log -n 1 --format="%s" HEAD` + commit_hash=` git log -n 1 --format="%H" HEAD` + + #default commit message uses last title if a custom one is not supplied + if [[ -z $commit_message ]]; then + commit_message="publish: $commit_title" + fi + + #append hash to commit message unless no hash flag was found + if [ $append_hash = true ]; then + commit_message="$commit_message"$'\n\n'"generated from commit $commit_hash" + fi + + previous_branch=`git rev-parse --abbrev-ref HEAD` + + if [ ! -d "$deploy_directory" ]; then + echo "Deploy directory '$deploy_directory' does not exist. Aborting." >&2 + return 1 + fi + + # must use short form of flag in ls for compatibility with macOS and BSD + if [[ -z `ls -A "$deploy_directory" 2> /dev/null` && -z $allow_empty ]]; then + echo "Deploy directory '$deploy_directory' is empty. Aborting. If you're sure you want to deploy an empty tree, use the --allow-empty / -e flag." >&2 + return 1 + fi + + if git ls-remote --exit-code $repo "refs/heads/$deploy_branch" ; then + # deploy_branch exists in $repo; make sure we have the latest version + + disable_expanded_output + git fetch --force $repo $deploy_branch:$deploy_branch + enable_expanded_output + fi + + # check if deploy_branch exists locally + if git show-ref --verify --quiet "refs/heads/$deploy_branch" + then incremental_deploy + else initial_deploy + fi + + restore_head +} + +initial_deploy() { + git --work-tree "$deploy_directory" checkout --orphan $deploy_branch + git --work-tree "$deploy_directory" add --all + commit+push +} + +incremental_deploy() { + #make deploy_branch the current branch + git symbolic-ref HEAD refs/heads/$deploy_branch + #put the previously committed contents of deploy_branch into the index + git --work-tree "$deploy_directory" reset --mixed --quiet + git --work-tree "$deploy_directory" add --all + + set +o errexit + diff=$(git --work-tree "$deploy_directory" diff --exit-code --quiet HEAD --)$? + set -o errexit + case $diff in + 0) echo No changes to files in $deploy_directory. Skipping commit.;; + 1) commit+push;; + *) + echo git diff exited with code $diff. Aborting. Staying on branch $deploy_branch so you can debug. To switch back to main, use: git symbolic-ref HEAD refs/heads/main && git reset --mixed >&2 + return $diff + ;; + esac +} + +commit+push() { + set_user_id + git --work-tree "$deploy_directory" commit -m "$commit_message" + + disable_expanded_output + #--quiet is important here to avoid outputting the repo URL, which may contain a secret token + git push --quiet $repo $deploy_branch + enable_expanded_output +} + +#echo expanded commands as they are executed (for debugging) +enable_expanded_output() { + if [ $verbose ]; then + set -o xtrace + set +o verbose + fi +} + +#this is used to avoid outputting the repo URL, which may contain a secret token +disable_expanded_output() { + if [ $verbose ]; then + set +o xtrace + set -o verbose + fi +} + +set_user_id() { + if [[ -z `git config user.name` ]]; then + git config user.name "$default_username" + fi + if [[ -z `git config user.email` ]]; then + git config user.email "$default_email" + fi +} + +restore_head() { + if [[ $previous_branch = "HEAD" ]]; then + #we weren't on any branch before, so just set HEAD back to the commit it was on + git update-ref --no-deref HEAD $commit_hash $deploy_branch + else + git symbolic-ref HEAD refs/heads/$previous_branch + fi + + git reset --mixed +} + +filter() { + sed -e "s|$repo|\$repo|g" +} + +sanitize() { + "$@" 2> >(filter 1>&2) | filter +} + +parse_args "$@" + +if [[ ${source_only} ]]; then + run_build +elif [[ ${push_only} ]]; then + main "$@" +else + run_build + main "$@" +fi diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 00000000000..8f1c475b2f0 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,18 @@ +version: '3' +services: + slate: + build: . + image: local/oybayar-docs-slate + command: ["build"] + volumes: + - "./build:/srv/slate/build" + - "./source:/srv/slate/source" + slate_serve: + image: local/oybayar-docs-slate + command: ["serve"] + ports: + - "4567:4567" + volumes: + - "./source:/srv/slate/source" + depends_on: + - slate diff --git a/lib/monokai_sublime_slate.rb b/lib/monokai_sublime_slate.rb new file mode 100644 index 00000000000..cd2de33172d --- /dev/null +++ b/lib/monokai_sublime_slate.rb @@ -0,0 +1,95 @@ +# -*- coding: utf-8 -*- # +# frozen_string_literal: true + +# this is based on https://github.com/rouge-ruby/rouge/blob/master/lib/rouge/themes/monokai_sublime.rb +# but without the added background, and changed styling for JSON keys to be soft_yellow instead of white + +module Rouge + module Themes + class MonokaiSublimeSlate < CSSTheme + name 'monokai.sublime.slate' + + palette :black => '#000000' + palette :bright_green => '#a6e22e' + palette :bright_pink => '#f92672' + palette :carmine => '#960050' + palette :dark => '#49483e' + palette :dark_grey => '#888888' + palette :dark_red => '#aa0000' + palette :dimgrey => '#75715e' + palette :emperor => '#555555' + palette :grey => '#999999' + palette :light_grey => '#aaaaaa' + palette :light_violet => '#ae81ff' + palette :soft_cyan => '#66d9ef' + palette :soft_yellow => '#e6db74' + palette :very_dark => '#1e0010' + palette :whitish => '#f8f8f2' + palette :orange => '#f6aa11' + palette :white => '#ffffff' + + style Generic::Heading, :fg => :grey + style Literal::String::Regex, :fg => :orange + style Generic::Output, :fg => :dark_grey + style Generic::Prompt, :fg => :emperor + style Generic::Strong, :bold => false + style Generic::Subheading, :fg => :light_grey + style Name::Builtin, :fg => :orange + style Comment::Multiline, + Comment::Preproc, + Comment::Single, + Comment::Special, + Comment, :fg => :dimgrey + style Error, + Generic::Error, + Generic::Traceback, :fg => :carmine + style Generic::Deleted, + Generic::Inserted, + Generic::Emph, :fg => :dark + style Keyword::Constant, + Keyword::Declaration, + Keyword::Reserved, + Name::Constant, + Keyword::Type, :fg => :soft_cyan + style Literal::Number::Float, + Literal::Number::Hex, + Literal::Number::Integer::Long, + Literal::Number::Integer, + Literal::Number::Oct, + Literal::Number, + Literal::String::Char, + Literal::String::Escape, + Literal::String::Symbol, :fg => :light_violet + style Literal::String::Doc, + Literal::String::Double, + Literal::String::Backtick, + Literal::String::Heredoc, + Literal::String::Interpol, + Literal::String::Other, + Literal::String::Single, + Literal::String, :fg => :soft_yellow + style Name::Attribute, + Name::Class, + Name::Decorator, + Name::Exception, + Name::Function, :fg => :bright_green + style Name::Variable::Class, + Name::Namespace, + Name::Entity, + Name::Builtin::Pseudo, + Name::Variable::Global, + Name::Variable::Instance, + Name::Variable, + Text::Whitespace, + Text, + Name, :fg => :white + style Name::Label, :fg => :bright_pink + style Operator::Word, + Name::Tag, + Keyword, + Keyword::Namespace, + Keyword::Pseudo, + Operator, :fg => :bright_pink + end + end + end diff --git a/lib/multilang.rb b/lib/multilang.rb new file mode 100644 index 00000000000..36fbe5b1f07 --- /dev/null +++ b/lib/multilang.rb @@ -0,0 +1,16 @@ +module Multilang + def block_code(code, full_lang_name) + if full_lang_name + parts = full_lang_name.split('--') + rouge_lang_name = (parts) ? parts[0] : "" # just parts[0] here causes null ref exception when no language specified + super(code, rouge_lang_name).sub("highlight #{rouge_lang_name}") do |match| + match + " tab-" + full_lang_name + end + else + super(code, full_lang_name) + end + end +end + +require 'middleman-core/renderers/redcarpet' +Middleman::Renderers::MiddlemanRedcarpetHTML.send :include, Multilang diff --git a/lib/nesting_unique_head.rb b/lib/nesting_unique_head.rb new file mode 100644 index 00000000000..01278371c17 --- /dev/null +++ b/lib/nesting_unique_head.rb @@ -0,0 +1,22 @@ +# Nested unique header generation +require 'middleman-core/renderers/redcarpet' + +class NestingUniqueHeadCounter < Middleman::Renderers::MiddlemanRedcarpetHTML + def initialize + super + @@headers_history = {} if !defined?(@@headers_history) + end + + def header(text, header_level) + friendly_text = text.gsub(/<[^>]*>/,"").parameterize + @@headers_history[header_level] = text.parameterize + + if header_level > 1 + for i in (header_level - 1).downto(1) + friendly_text.prepend("#{@@headers_history[i]}-") if @@headers_history.key?(i) + end + end + + return "#{text}" + end +end diff --git a/lib/oy_markdown_renderer.rb b/lib/oy_markdown_renderer.rb new file mode 100644 index 00000000000..1b3f62ea348 --- /dev/null +++ b/lib/oy_markdown_renderer.rb @@ -0,0 +1,70 @@ +require 'middleman-core/renderers/redcarpet' +require 'digest' + +class OyMarkdownRenderer < Middleman::Renderers::MiddlemanRedcarpetHTML + + def initialize + super + @head_count = {} + @previous = {} + end + + # Unique header generation + def header(text, header_level) + friendly_text = text.gsub(/<[^>]*>/,"").parameterize + if(friendly_text.include? "release-") + idxTrim = friendly_text.index('release-') + friendly_text = friendly_text[0, idxTrim] + if friendly_text[friendly_text.length-1] == '-' + friendly_text = friendly_text[0, friendly_text.length-1] + end + end + if(friendly_text.include? "new") + idxTrim = friendly_text.index('new') + friendly_text = friendly_text[0, idxTrim] + if friendly_text[friendly_text.length-1] == '-' + friendly_text = friendly_text[0, friendly_text.length-1] + end + end + if friendly_text.strip.length == 0 + # Looks like parameterize removed the whole thing! It removes many unicode + # characters like Chinese and Russian. To get a unique URL, let's just + # URI escape the whole header + friendly_text = Digest::SHA1.hexdigest(text)[0,10] + end + if (@previous[header_level] == nil )|| (@previous[header_level] != friendly_text) + @previous[header_level] = friendly_text + end + + if header_level > 1 + friendly_text += "-#{@previous[header_level-1]}" + end + + @head_count[friendly_text] ||= 0 + @head_count[friendly_text] += 1 + if @head_count[friendly_text] > 1 + friendly_text += "-#{@head_count[friendly_text]}" + end + if(text.include? "RELEASE") + idxTrim = text.index("RELEASE-") + content = text[0,idxTrim] + releaseDate = text[idxTrim+"RELEASE-".length, text.length] + return "#{content} release on #{releaseDate} " + elsif(text.include? "NEW") + idxTrim = text.index("NEW") + content = text[0,idxTrim] + return "#{content} NEW " + else + return "#{text}" + end + end + + # Reduce jumpy-ness when loading multiple images + def image(link, title, alt_text) + # 300x300 transparent svg for placeholder to keep overall structure intact + svg_placeholder = "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9Ii0xNTAgLTE1MCA0MDAgNDAwIiBwcmVzZXJ2ZUFzcGVjdFJhdGlvPSJ4TWlkWU1pZCIgd2lkdGg9IjMwMCAiIGhlaWdodD0iMzAwIiBzdHlsZT0ic2hhcGUtcmVuZGVyaW5nOiBhdXRvOyBkaXNwbGF5OiBibG9jazsgYmFja2dyb3VuZDogcmdiKDI1NSwgMjU1LCAyNTUpOyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiPjxnPjxjaXJjbGUgc3Ryb2tlLWRhc2hhcnJheT0iMTY0LjkzMzYxNDMxMzQ2NDE1IDU2Ljk3Nzg3MTQzNzgyMTM4IiByPSIzNSIgc3Ryb2tlLXdpZHRoPSIxMCIgc3Ryb2tlPSIjMGQ0N2ExIiByYWRpdXM9IjEwMCIgZmlsbD0ibm9uZSIgY3k9IjUwIiBjeD0iNTAiPjxhbmltYXRlVHJhbnNmb3JtIGtleVRpbWVzPSIwOzEiIHZhbHVlcz0iMCA1MCA1MDszNjAgNTAgNTAiIGR1cj0iMXMiIHJlcGVhdENvdW50PSJpbmRlZmluaXRlIiB0eXBlPSJyb3RhdGUiIGF0dHJpYnV0ZU5hbWU9InRyYW5zZm9ybSI+PC9hbmltYXRlVHJhbnNmb3JtPjwvY2lyY2xlPjxnPjwvZz48L2c+PC9zdmc+" + + # lazy load image to reduce page jumps + return %{#{alt_text}} + end +end diff --git a/lib/oy_theme.rb b/lib/oy_theme.rb new file mode 100644 index 00000000000..629c98f9039 --- /dev/null +++ b/lib/oy_theme.rb @@ -0,0 +1,96 @@ +# -*- coding: utf-8 -*- # +# frozen_string_literal: true + +# this is based on https://github.com/rouge-ruby/rouge/blob/master/lib/rouge/themes/monokai_sublime.rb +# but without the added background, and changed styling for JSON keys to be soft_yellow instead of white + +module Rouge + module Themes + class OyTheme < CSSTheme + name 'oy.theme.slate' + + palette :black => '#000000' + palette :bright_green => '#a6e22e' + palette :bright_pink => '#f92672' + palette :carmine => '#960050' + palette :dark => '#49483e' + palette :dark_grey => '#888888' + palette :dark_red => '#aa0000' + palette :dimgrey => '#75715e' + palette :emperor => '#555555' + palette :grey => '#999999' + palette :light_grey => '#aaaaaa' + palette :light_violet => '#ae81ff' + palette :soft_cyan => '#66d9ef' + palette :soft_yellow => '#e6db74' + palette :very_dark => '#1e0010' + palette :whitish => '#f8f8f2' + palette :orange => '#ff860F' + palette :yellow => '#f7d250' + palette :white => '#ffffff' + + style Generic::Heading, :fg => :grey + style Literal::String::Regex, :fg => :orange + style Generic::Output, :fg => :dark_grey + style Generic::Prompt, :fg => :emperor + style Generic::Strong, :bold => false + style Generic::Subheading, :fg => :light_grey + style Name::Builtin, :fg => :orange + style Comment::Multiline, + Comment::Preproc, + Comment::Single, + Comment::Special, + Comment, :fg => :dimgrey + style Error, + Generic::Error, + Generic::Traceback, :fg => :carmine + style Generic::Deleted, + Generic::Inserted, + Generic::Emph, :fg => :dark + style Keyword::Constant, + Keyword::Declaration, + Keyword::Reserved, + Name::Constant, + Keyword::Type, :fg => :soft_cyan + style Literal::Number::Float, + Literal::Number::Hex, + Literal::Number::Integer::Long, + Literal::Number::Integer, + Literal::Number::Oct, + Literal::Number, + Literal::String::Char, + Literal::String::Escape, + Literal::String::Symbol, :fg => :light_violet + style Literal::String::Doc, + Literal::String::Double, + Literal::String::Backtick, + Literal::String::Heredoc, + Literal::String::Interpol, + Literal::String::Other, + Literal::String::Single, + Literal::String, :fg => :yellow + style Name::Attribute, + Name::Class, + Name::Decorator, + Name::Exception, + Name::Function, :fg => :bright_green + style Name::Variable::Class, + Name::Namespace, + Name::Entity, + Name::Builtin::Pseudo, + Name::Variable::Global, + Name::Variable::Instance, + Name::Variable, + Text::Whitespace, + Text, + Name, :fg => :white + style Name::Label, :fg => :orange + style Operator::Word, + Name::Tag, + Keyword, + Keyword::Namespace, + Keyword::Pseudo, + Operator, :fg => :orange + end + end + end diff --git a/lib/toc_data.rb b/lib/toc_data.rb new file mode 100644 index 00000000000..3185ce36281 --- /dev/null +++ b/lib/toc_data.rb @@ -0,0 +1,45 @@ +require 'nokogiri' + +def toc_data(page_content) + html_doc = Nokogiri::HTML::DocumentFragment.parse(page_content) + + # get a flat list of headers + headers = [] + html_doc.css('h1, h2, h3').each do |header| + if header.attribute('type').to_s == 'beta' + idxTrim = header.children.to_s.index("BETA" + headers.push({ + id: header.attribute('id').to_s, + content: content, + title: header.children.to_s.gsub(/<[^>]*>/, ''), + level: header.name[1].to_i, + releases: true, + children: [] + }) + else + headers.push({ + id: header.attribute('id').to_s, + content: header.children, + title: header.children.to_s.gsub(/<[^>]*>/, ''), + level: header.name[1].to_i, + releases: false, + children: [] + }) + end + end + + [3,2].each do |header_level| + header_to_nest = nil + headers = headers.reject do |header| + if header[:level] == header_level + header_to_nest[:children].push header if header_to_nest + true + else + header_to_nest = header if header[:level] < header_level + false + end + end + end + headers +end diff --git a/locales/en.yml b/locales/en.yml new file mode 100644 index 00000000000..910dff08814 --- /dev/null +++ b/locales/en.yml @@ -0,0 +1,10 @@ +--- +en: + title: "Product Documentation" + home: "Home" + register: "Register Now" + login: "Log In" + api: "API Docs" + lang: "ID" + search: "Search Documentation" + \ No newline at end of file diff --git a/locales/id.yml b/locales/id.yml new file mode 100644 index 00000000000..058ac5381dc --- /dev/null +++ b/locales/id.yml @@ -0,0 +1,10 @@ +--- +id: + title: "Dokumentasi Produk" + home: "Beranda" + register: "Daftar Sekarang" + login: "Masuk Dashboard" + api: "Dok API" + lang: "EN" + search: "Pencarian Dokumentasi" + \ No newline at end of file diff --git a/slate.sh b/slate.sh new file mode 100755 index 00000000000..d6f1bd9d5f1 --- /dev/null +++ b/slate.sh @@ -0,0 +1,248 @@ +#!/usr/bin/env bash +set -o errexit #abort if any command fails + +me=$(basename "$0") + +help_message="\ +Usage: $me [] [] +Run commands related to the slate process. + +Commands: + + serve Run the middleman server process, useful for + development. + build Run the build process. + deploy Will build and deploy files to branch. Use + --no-build to only deploy. + +Global Options: + + -h, --help Show this help information. + -v, --verbose Increase verbosity. Useful for debugging. + +Deploy options: + -e, --allow-empty Allow deployment of an empty directory. + -m, --message MESSAGE Specify the message used when committing on the + deploy branch. + -n, --no-hash Don't append the source commit's hash to the deploy + commit's message. + --no-build Do not build the source files. +" + + +run_serve() { + exec bundle exec middleman serve --watcher-force-polling +} + +run_build() { + bundle exec middleman build --clean --watcher-disable +} + +parse_args() { + # Set args from a local environment file. + if [ -e ".env" ]; then + source .env + fi + + command= + + # Parse arg flags + # If something is exposed as an environment variable, set/overwrite it + # here. Otherwise, set/overwrite the internal variable instead. + while : ; do + if [[ $1 = "-h" || $1 = "--help" ]]; then + echo "$help_message" + exit 0 + elif [[ $1 = "-v" || $1 = "--verbose" ]]; then + verbose=true + shift + elif [[ $1 = "-e" || $1 = "--allow-empty" ]]; then + allow_empty=true + shift + elif [[ ( $1 = "-m" || $1 = "--message" ) && -n $2 ]]; then + commit_message=$2 + shift 2 + elif [[ $1 = "-n" || $1 = "--no-hash" ]]; then + GIT_DEPLOY_APPEND_HASH=false + shift + elif [[ $1 = "--no-build" ]]; then + no_build=true + shift + elif [[ $1 = "serve" || $1 = "build" || $1 = "deploy" ]]; then + if [ ! -z "${command}" ]; then + >&2 echo "You can only specify one command." + exit 1 + fi + command=$1 + shift + elif [ -z $1 ]; then + break + fi + done + + if [ -z "${command}" ]; then + >&2 echo "Command not specified." + exit 1 + fi + + # Set internal option vars from the environment and arg flags. All internal + # vars should be declared here, with sane defaults if applicable. + + # Source directory & target branch. + deploy_directory=build + deploy_branch=gh-pages + + #if no user identity is already set in the current git environment, use this: + default_username=${GIT_DEPLOY_USERNAME:-deploy.sh} + default_email=${GIT_DEPLOY_EMAIL:-} + + #repository to deploy to. must be readable and writable. + repo=origin + + #append commit hash to the end of message by default + append_hash=${GIT_DEPLOY_APPEND_HASH:-true} +} + +main() { + enable_expanded_output + + if ! git diff --exit-code --quiet --cached; then + echo Aborting due to uncommitted changes in the index >&2 + return 1 + fi + + commit_title=`git log -n 1 --format="%s" HEAD` + commit_hash=` git log -n 1 --format="%H" HEAD` + + #default commit message uses last title if a custom one is not supplied + if [[ -z $commit_message ]]; then + commit_message="publish: $commit_title" + fi + + #append hash to commit message unless no hash flag was found + if [ $append_hash = true ]; then + commit_message="$commit_message"$'\n\n'"generated from commit $commit_hash" + fi + + previous_branch=`git rev-parse --abbrev-ref HEAD` + + if [ ! -d "$deploy_directory" ]; then + echo "Deploy directory '$deploy_directory' does not exist. Aborting." >&2 + return 1 + fi + + # must use short form of flag in ls for compatibility with macOS and BSD + if [[ -z `ls -A "$deploy_directory" 2> /dev/null` && -z $allow_empty ]]; then + echo "Deploy directory '$deploy_directory' is empty. Aborting. If you're sure you want to deploy an empty tree, use the --allow-empty / -e flag." >&2 + return 1 + fi + + if git ls-remote --exit-code $repo "refs/heads/$deploy_branch" ; then + # deploy_branch exists in $repo; make sure we have the latest version + + disable_expanded_output + git fetch --force $repo $deploy_branch:$deploy_branch + enable_expanded_output + fi + + # check if deploy_branch exists locally + if git show-ref --verify --quiet "refs/heads/$deploy_branch" + then incremental_deploy + else initial_deploy + fi + + restore_head +} + +initial_deploy() { + git --work-tree "$deploy_directory" checkout --orphan $deploy_branch + git --work-tree "$deploy_directory" add --all + commit+push +} + +incremental_deploy() { + #make deploy_branch the current branch + git symbolic-ref HEAD refs/heads/$deploy_branch + #put the previously committed contents of deploy_branch into the index + git --work-tree "$deploy_directory" reset --mixed --quiet + git --work-tree "$deploy_directory" add --all + + set +o errexit + diff=$(git --work-tree "$deploy_directory" diff --exit-code --quiet HEAD --)$? + set -o errexit + case $diff in + 0) echo No changes to files in $deploy_directory. Skipping commit.;; + 1) commit+push;; + *) + echo git diff exited with code $diff. Aborting. Staying on branch $deploy_branch so you can debug. To switch back to main, use: git symbolic-ref HEAD refs/heads/main && git reset --mixed >&2 + return $diff + ;; + esac +} + +commit+push() { + set_user_id + git --work-tree "$deploy_directory" commit -m "$commit_message" + + disable_expanded_output + #--quiet is important here to avoid outputting the repo URL, which may contain a secret token + git push --quiet $repo $deploy_branch + enable_expanded_output +} + +#echo expanded commands as they are executed (for debugging) +enable_expanded_output() { + if [ $verbose ]; then + set -o xtrace + set +o verbose + fi +} + +#this is used to avoid outputting the repo URL, which may contain a secret token +disable_expanded_output() { + if [ $verbose ]; then + set +o xtrace + set -o verbose + fi +} + +set_user_id() { + if [[ -z `git config user.name` ]]; then + git config user.name "$default_username" + fi + if [[ -z `git config user.email` ]]; then + git config user.email "$default_email" + fi +} + +restore_head() { + if [[ $previous_branch = "HEAD" ]]; then + #we weren't on any branch before, so just set HEAD back to the commit it was on + git update-ref --no-deref HEAD $commit_hash $deploy_branch + else + git symbolic-ref HEAD refs/heads/$previous_branch + fi + + git reset --mixed +} + +filter() { + sed -e "s|$repo|\$repo|g" +} + +sanitize() { + "$@" 2> >(filter 1>&2) | filter +} + +parse_args "$@" + +if [ "${command}" = "serve" ]; then + run_serve +elif [[ "${command}" = "build" ]]; then + run_build +elif [[ ${command} = "deploy" ]]; then + if [[ ${no_build} != true ]]; then + run_build + fi + main "$@" +fi diff --git a/source/CNAME b/source/CNAME new file mode 100644 index 00000000000..d81f83c31cd --- /dev/null +++ b/source/CNAME @@ -0,0 +1 @@ +docs.oyindonesia.com diff --git a/source/fonts/slate.eot b/source/fonts/slate.eot old mode 100755 new mode 100644 diff --git a/source/fonts/slate.svg b/source/fonts/slate.svg old mode 100755 new mode 100644 diff --git a/source/fonts/slate.ttf b/source/fonts/slate.ttf old mode 100755 new mode 100644 diff --git a/source/fonts/slate.woff b/source/fonts/slate.woff old mode 100755 new mode 100644 diff --git a/source/fonts/slate.woff2 b/source/fonts/slate.woff2 old mode 100755 new mode 100644 diff --git a/source/images/3p1p_preview.png b/source/images/3p1p_preview.png new file mode 100644 index 00000000000..9749e82d3de Binary files /dev/null and b/source/images/3p1p_preview.png differ diff --git a/source/images/API_Biller.png b/source/images/API_Biller.png new file mode 100644 index 00000000000..ef9551bd0ca Binary files /dev/null and b/source/images/API_Biller.png differ diff --git a/source/images/As_Child_AccState_2b_Waiting_approval.png b/source/images/As_Child_AccState_2b_Waiting_approval.png new file mode 100644 index 00000000000..7257e7b8422 Binary files /dev/null and b/source/images/As_Child_AccState_2b_Waiting_approval.png differ diff --git a/source/images/As_Child_AccState_4b_2_Requests_to_connect.png b/source/images/As_Child_AccState_4b_2_Requests_to_connect.png new file mode 100644 index 00000000000..afda9ff3c62 Binary files /dev/null and b/source/images/As_Child_AccState_4b_2_Requests_to_connect.png differ diff --git a/source/images/As_Child_AccState_4e_Confirmation_to_connect.png b/source/images/As_Child_AccState_4e_Confirmation_to_connect.png new file mode 100644 index 00000000000..ae7fce7b089 Binary files /dev/null and b/source/images/As_Child_AccState_4e_Confirmation_to_connect.png differ diff --git a/source/images/As_Child_AccState_4h_Success_add_new_head_company.png b/source/images/As_Child_AccState_4h_Success_add_new_head_company.png new file mode 100644 index 00000000000..6a42724e679 Binary files /dev/null and b/source/images/As_Child_AccState_4h_Success_add_new_head_company.png differ diff --git a/source/images/As_Parent_Subs_1_Initial(2).png b/source/images/As_Parent_Subs_1_Initial(2).png new file mode 100644 index 00000000000..9e7de597806 Binary files /dev/null and b/source/images/As_Parent_Subs_1_Initial(2).png differ diff --git a/source/images/As_Parent_Subs_5a_Top_Up.png b/source/images/As_Parent_Subs_5a_Top_Up.png new file mode 100644 index 00000000000..521c248b292 Binary files /dev/null and b/source/images/As_Parent_Subs_5a_Top_Up.png differ diff --git a/source/images/As_Parent_Subs_6_Add_Subsidiary_Type_Username.png b/source/images/As_Parent_Subs_6_Add_Subsidiary_Type_Username.png new file mode 100644 index 00000000000..50c712ed5a1 Binary files /dev/null and b/source/images/As_Parent_Subs_6_Add_Subsidiary_Type_Username.png differ diff --git a/source/images/As_Parent_Subs_Existing_Username_Not_Connected_with_any_parent.png b/source/images/As_Parent_Subs_Existing_Username_Not_Connected_with_any_parent.png new file mode 100644 index 00000000000..d79a032fb2c Binary files /dev/null and b/source/images/As_Parent_Subs_Existing_Username_Not_Connected_with_any_parent.png differ diff --git a/source/images/Choose_SoF_2_Choose_SoF_subsidiary.png b/source/images/Choose_SoF_2_Choose_SoF_subsidiary.png new file mode 100644 index 00000000000..3d888d88d86 Binary files /dev/null and b/source/images/Choose_SoF_2_Choose_SoF_subsidiary.png differ diff --git a/source/images/Choose_SoF_4_Type_or_search_sub.png b/source/images/Choose_SoF_4_Type_or_search_sub.png new file mode 100644 index 00000000000..f4def4e05dd Binary files /dev/null and b/source/images/Choose_SoF_4_Type_or_search_sub.png differ diff --git a/source/images/Flow_API_Biller.png b/source/images/Flow_API_Biller.png new file mode 100644 index 00000000000..4c6bc0f772a Binary files /dev/null and b/source/images/Flow_API_Biller.png differ diff --git a/source/images/Login_1.png b/source/images/Login_1.png new file mode 100644 index 00000000000..3e4630099bf Binary files /dev/null and b/source/images/Login_1.png differ diff --git a/source/images/Login_OTP.jpg b/source/images/Login_OTP.jpg new file mode 100644 index 00000000000..13b523bbb35 Binary files /dev/null and b/source/images/Login_OTP.jpg differ diff --git a/source/images/Login_OTP.png b/source/images/Login_OTP.png new file mode 100644 index 00000000000..2a7662af106 Binary files /dev/null and b/source/images/Login_OTP.png differ diff --git a/source/images/MEM_Add_Sub_Entity_1.png b/source/images/MEM_Add_Sub_Entity_1.png new file mode 100644 index 00000000000..be423d0f26d Binary files /dev/null and b/source/images/MEM_Add_Sub_Entity_1.png differ diff --git a/source/images/MEM_Add_Sub_Entity_2.jpg b/source/images/MEM_Add_Sub_Entity_2.jpg new file mode 100644 index 00000000000..dfab9a4e1f5 Binary files /dev/null and b/source/images/MEM_Add_Sub_Entity_2.jpg differ diff --git a/source/images/MEM_select_subentity_disbursement.png b/source/images/MEM_select_subentity_disbursement.png new file mode 100644 index 00000000000..82665ffc1a8 Binary files /dev/null and b/source/images/MEM_select_subentity_disbursement.png differ diff --git a/source/images/MEM_select_subentity_paymentlink.png b/source/images/MEM_select_subentity_paymentlink.png new file mode 100644 index 00000000000..e2ee5004353 Binary files /dev/null and b/source/images/MEM_select_subentity_paymentlink.png differ diff --git a/source/images/Payment_Routing_Flow.png b/source/images/Payment_Routing_Flow.png new file mode 100644 index 00000000000..dba40656ca4 Binary files /dev/null and b/source/images/Payment_Routing_Flow.png differ diff --git a/source/images/accepting-payments.svg b/source/images/accepting-payments.svg new file mode 100644 index 00000000000..fc7bb0b4dc7 --- /dev/null +++ b/source/images/accepting-payments.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/source/images/acceptingPayments/api-account-linking/dashboard.webp b/source/images/acceptingPayments/api-account-linking/dashboard.webp new file mode 100644 index 00000000000..70c86ca016f Binary files /dev/null and b/source/images/acceptingPayments/api-account-linking/dashboard.webp differ diff --git a/source/images/acceptingPayments/api-account-linking/payment-flow-api-account-linking.webp b/source/images/acceptingPayments/api-account-linking/payment-flow-api-account-linking.webp new file mode 100644 index 00000000000..6d8a84dad92 Binary files /dev/null and b/source/images/acceptingPayments/api-account-linking/payment-flow-api-account-linking.webp differ diff --git a/source/images/acceptingPayments/api-account-linking/payment-flow-api-get-e-wallet-balance.webp b/source/images/acceptingPayments/api-account-linking/payment-flow-api-get-e-wallet-balance.webp new file mode 100644 index 00000000000..dd2b412263f Binary files /dev/null and b/source/images/acceptingPayments/api-account-linking/payment-flow-api-get-e-wallet-balance.webp differ diff --git a/source/images/acceptingPayments/api-account-linking/payment-flow-unlink-account-via-api-accoung-linking.webp b/source/images/acceptingPayments/api-account-linking/payment-flow-unlink-account-via-api-accoung-linking.webp new file mode 100644 index 00000000000..5356f36e416 Binary files /dev/null and b/source/images/acceptingPayments/api-account-linking/payment-flow-unlink-account-via-api-accoung-linking.webp differ diff --git a/source/images/acceptingPayments/api-account-linking/payment-flow-unlink-account-via-e-wallet-app.webp b/source/images/acceptingPayments/api-account-linking/payment-flow-unlink-account-via-e-wallet-app.webp new file mode 100644 index 00000000000..2be4f4a1201 Binary files /dev/null and b/source/images/acceptingPayments/api-account-linking/payment-flow-unlink-account-via-e-wallet-app.webp differ diff --git a/source/images/acceptingPayments/api-e-wallet-aggragator/dashboard-callback.webp b/source/images/acceptingPayments/api-e-wallet-aggragator/dashboard-callback.webp new file mode 100644 index 00000000000..e68df532bc4 Binary files /dev/null and b/source/images/acceptingPayments/api-e-wallet-aggragator/dashboard-callback.webp differ diff --git a/source/images/acceptingPayments/api-e-wallet-aggragator/dashboard-developer-option.webp b/source/images/acceptingPayments/api-e-wallet-aggragator/dashboard-developer-option.webp new file mode 100644 index 00000000000..9e4e987623b Binary files /dev/null and b/source/images/acceptingPayments/api-e-wallet-aggragator/dashboard-developer-option.webp differ diff --git a/source/images/acceptingPayments/api-e-wallet-aggragator/dashboard-selesai.webp b/source/images/acceptingPayments/api-e-wallet-aggragator/dashboard-selesai.webp new file mode 100644 index 00000000000..67ebd7649ad Binary files /dev/null and b/source/images/acceptingPayments/api-e-wallet-aggragator/dashboard-selesai.webp differ diff --git a/source/images/acceptingPayments/api-e-wallet-aggragator/dashboard-waiting-payment.webp b/source/images/acceptingPayments/api-e-wallet-aggragator/dashboard-waiting-payment.webp new file mode 100644 index 00000000000..c255b998e78 Binary files /dev/null and b/source/images/acceptingPayments/api-e-wallet-aggragator/dashboard-waiting-payment.webp differ diff --git a/source/images/acceptingPayments/api-e-wallet-aggragator/payment-flow.webp b/source/images/acceptingPayments/api-e-wallet-aggragator/payment-flow.webp new file mode 100644 index 00000000000..9ffddc04437 Binary files /dev/null and b/source/images/acceptingPayments/api-e-wallet-aggragator/payment-flow.webp differ diff --git a/source/images/acceptingPayments/api-payment-routing/dashboard.webp b/source/images/acceptingPayments/api-payment-routing/dashboard.webp new file mode 100644 index 00000000000..8c58c3593f9 Binary files /dev/null and b/source/images/acceptingPayments/api-payment-routing/dashboard.webp differ diff --git a/source/images/acceptingPayments/api-payment-routing/payment-flow-with-ui-scheme.webp b/source/images/acceptingPayments/api-payment-routing/payment-flow-with-ui-scheme.webp new file mode 100644 index 00000000000..ba50a75de9d Binary files /dev/null and b/source/images/acceptingPayments/api-payment-routing/payment-flow-with-ui-scheme.webp differ diff --git a/source/images/acceptingPayments/api-payment-routing/payment-flow-without-ui-scheme.webp b/source/images/acceptingPayments/api-payment-routing/payment-flow-without-ui-scheme.webp new file mode 100644 index 00000000000..913a66c169e Binary files /dev/null and b/source/images/acceptingPayments/api-payment-routing/payment-flow-without-ui-scheme.webp differ diff --git a/source/images/acceptingPayments/payment-link/creating-payment-link/creation.webp b/source/images/acceptingPayments/payment-link/creating-payment-link/creation.webp new file mode 100644 index 00000000000..af92cdedcd2 Binary files /dev/null and b/source/images/acceptingPayments/payment-link/creating-payment-link/creation.webp differ diff --git a/source/images/acceptingPayments/payment-link/features/copy-link.webp b/source/images/acceptingPayments/payment-link/features/copy-link.webp new file mode 100644 index 00000000000..d249d9f5d3e Binary files /dev/null and b/source/images/acceptingPayments/payment-link/features/copy-link.webp differ diff --git a/source/images/acceptingPayments/payment-link/features/dashboard-customized-payment-link.webp b/source/images/acceptingPayments/payment-link/features/dashboard-customized-payment-link.webp new file mode 100644 index 00000000000..beb3a2090d7 Binary files /dev/null and b/source/images/acceptingPayments/payment-link/features/dashboard-customized-payment-link.webp differ diff --git a/source/images/acceptingPayments/payment-link/features/dashboard-developer-option.webp b/source/images/acceptingPayments/payment-link/features/dashboard-developer-option.webp new file mode 100644 index 00000000000..b27112d28b7 Binary files /dev/null and b/source/images/acceptingPayments/payment-link/features/dashboard-developer-option.webp differ diff --git a/source/images/acceptingPayments/payment-link/features/dashboard-notif-for-receiver.webp b/source/images/acceptingPayments/payment-link/features/dashboard-notif-for-receiver.webp new file mode 100644 index 00000000000..1c8bb7335b3 Binary files /dev/null and b/source/images/acceptingPayments/payment-link/features/dashboard-notif-for-receiver.webp differ diff --git a/source/images/acceptingPayments/payment-link/features/dashboard-notif-for-sender.webp b/source/images/acceptingPayments/payment-link/features/dashboard-notif-for-sender.webp new file mode 100644 index 00000000000..3fec69c11e1 Binary files /dev/null and b/source/images/acceptingPayments/payment-link/features/dashboard-notif-for-sender.webp differ diff --git a/source/images/acceptingPayments/payment-link/features/dashboard-resend-callback.webp b/source/images/acceptingPayments/payment-link/features/dashboard-resend-callback.webp new file mode 100644 index 00000000000..439cd09c666 Binary files /dev/null and b/source/images/acceptingPayments/payment-link/features/dashboard-resend-callback.webp differ diff --git a/source/images/acceptingPayments/payment-link/features/dashboard-view.webp b/source/images/acceptingPayments/payment-link/features/dashboard-view.webp new file mode 100644 index 00000000000..306e3266031 Binary files /dev/null and b/source/images/acceptingPayments/payment-link/features/dashboard-view.webp differ diff --git a/source/images/acceptingPayments/payment-link/features/default-config.webp b/source/images/acceptingPayments/payment-link/features/default-config.webp new file mode 100644 index 00000000000..8d79f7cdd95 Binary files /dev/null and b/source/images/acceptingPayments/payment-link/features/default-config.webp differ diff --git a/source/images/acceptingPayments/payment-link/features/payment-link-after-changes.webp b/source/images/acceptingPayments/payment-link/features/payment-link-after-changes.webp new file mode 100644 index 00000000000..187f9633ac1 Binary files /dev/null and b/source/images/acceptingPayments/payment-link/features/payment-link-after-changes.webp differ diff --git a/source/images/acceptingPayments/payment-link/features/preview-payment-link.webp b/source/images/acceptingPayments/payment-link/features/preview-payment-link.webp new file mode 100644 index 00000000000..e8fad80c779 Binary files /dev/null and b/source/images/acceptingPayments/payment-link/features/preview-payment-link.webp differ diff --git a/source/images/acceptingPayments/payment-link/flow/payment-flow-one-time.webp b/source/images/acceptingPayments/payment-link/flow/payment-flow-one-time.webp new file mode 100644 index 00000000000..5b4ac945d0b Binary files /dev/null and b/source/images/acceptingPayments/payment-link/flow/payment-flow-one-time.webp differ diff --git a/source/images/acceptingPayments/payment-link/flow/payment-flow-reusable.webp b/source/images/acceptingPayments/payment-link/flow/payment-flow-reusable.webp new file mode 100644 index 00000000000..5c4e0839caa Binary files /dev/null and b/source/images/acceptingPayments/payment-link/flow/payment-flow-reusable.webp differ diff --git a/source/images/acceptingPayments/payment-link/flow/preview-payment-link.webp b/source/images/acceptingPayments/payment-link/flow/preview-payment-link.webp new file mode 100644 index 00000000000..e8fad80c779 Binary files /dev/null and b/source/images/acceptingPayments/payment-link/flow/preview-payment-link.webp differ diff --git a/source/images/acceptingPayments/payment-methods/bank-transfer-unique-code/payment-flow.webp b/source/images/acceptingPayments/payment-methods/bank-transfer-unique-code/payment-flow.webp new file mode 100644 index 00000000000..eecc1cc69bc Binary files /dev/null and b/source/images/acceptingPayments/payment-methods/bank-transfer-unique-code/payment-flow.webp differ diff --git a/source/images/acceptingPayments/payment-methods/bank-transfer-unique-code/payment-link-view.webp b/source/images/acceptingPayments/payment-methods/bank-transfer-unique-code/payment-link-view.webp new file mode 100644 index 00000000000..efc162e26d5 Binary files /dev/null and b/source/images/acceptingPayments/payment-methods/bank-transfer-unique-code/payment-link-view.webp differ diff --git a/source/images/acceptingPayments/payment-methods/bank-transfer-unique-code/simulating-callback.webp b/source/images/acceptingPayments/payment-methods/bank-transfer-unique-code/simulating-callback.webp new file mode 100644 index 00000000000..ccb24dc6b1c Binary files /dev/null and b/source/images/acceptingPayments/payment-methods/bank-transfer-unique-code/simulating-callback.webp differ diff --git a/source/images/acceptingPayments/payment-methods/bank-transfer-virtual-account/payment-flow.webp b/source/images/acceptingPayments/payment-methods/bank-transfer-virtual-account/payment-flow.webp new file mode 100644 index 00000000000..aeb8b613a63 Binary files /dev/null and b/source/images/acceptingPayments/payment-methods/bank-transfer-virtual-account/payment-flow.webp differ diff --git a/source/images/acceptingPayments/payment-methods/bank-transfer-virtual-account/simulating-callback.webp b/source/images/acceptingPayments/payment-methods/bank-transfer-virtual-account/simulating-callback.webp new file mode 100644 index 00000000000..5d01248bf80 Binary files /dev/null and b/source/images/acceptingPayments/payment-methods/bank-transfer-virtual-account/simulating-callback.webp differ diff --git a/source/images/acceptingPayments/payment-methods/cards/payment-flow-via-payment-link.webp b/source/images/acceptingPayments/payment-methods/cards/payment-flow-via-payment-link.webp new file mode 100644 index 00000000000..8a09e5269f5 Binary files /dev/null and b/source/images/acceptingPayments/payment-methods/cards/payment-flow-via-payment-link.webp differ diff --git a/source/images/acceptingPayments/payment-methods/cards/payment-flow-via-payment-routing.webp b/source/images/acceptingPayments/payment-methods/cards/payment-flow-via-payment-routing.webp new file mode 100644 index 00000000000..c2b1d05b5a7 Binary files /dev/null and b/source/images/acceptingPayments/payment-methods/cards/payment-flow-via-payment-routing.webp differ diff --git a/source/images/acceptingPayments/payment-methods/e-wallet/payment-flow-direct-payment.webp b/source/images/acceptingPayments/payment-methods/e-wallet/payment-flow-direct-payment.webp new file mode 100644 index 00000000000..ac764238b6b Binary files /dev/null and b/source/images/acceptingPayments/payment-methods/e-wallet/payment-flow-direct-payment.webp differ diff --git a/source/images/acceptingPayments/payment-methods/e-wallet/payment-flow-single-payment-push-notification.webp b/source/images/acceptingPayments/payment-methods/e-wallet/payment-flow-single-payment-push-notification.webp new file mode 100644 index 00000000000..80fbf276a32 Binary files /dev/null and b/source/images/acceptingPayments/payment-methods/e-wallet/payment-flow-single-payment-push-notification.webp differ diff --git a/source/images/acceptingPayments/payment-methods/e-wallet/payment-flow-single-payment-redirection.webp b/source/images/acceptingPayments/payment-methods/e-wallet/payment-flow-single-payment-redirection.webp new file mode 100644 index 00000000000..091b1a8794f Binary files /dev/null and b/source/images/acceptingPayments/payment-methods/e-wallet/payment-flow-single-payment-redirection.webp differ diff --git a/source/images/acceptingPayments/payment-methods/e-wallet/payment-link-view.webp b/source/images/acceptingPayments/payment-methods/e-wallet/payment-link-view.webp new file mode 100644 index 00000000000..77f2d0a43d3 Binary files /dev/null and b/source/images/acceptingPayments/payment-methods/e-wallet/payment-link-view.webp differ diff --git a/source/images/acceptingPayments/payment-methods/e-wallet/preview-dashboard.webp b/source/images/acceptingPayments/payment-methods/e-wallet/preview-dashboard.webp new file mode 100644 index 00000000000..fa9ae8d9386 Binary files /dev/null and b/source/images/acceptingPayments/payment-methods/e-wallet/preview-dashboard.webp differ diff --git a/source/images/acceptingPayments/payment-methods/e-wallet/simulating-callback.webp b/source/images/acceptingPayments/payment-methods/e-wallet/simulating-callback.webp new file mode 100644 index 00000000000..189f753277b Binary files /dev/null and b/source/images/acceptingPayments/payment-methods/e-wallet/simulating-callback.webp differ diff --git a/source/images/acceptingPayments/payment-methods/qr-code-qris/payment-flow.webp b/source/images/acceptingPayments/payment-methods/qr-code-qris/payment-flow.webp new file mode 100644 index 00000000000..84965d09a2c Binary files /dev/null and b/source/images/acceptingPayments/payment-methods/qr-code-qris/payment-flow.webp differ diff --git a/source/images/acceptingPayments/payment-methods/qr-code-qris/payment-link-view.webp b/source/images/acceptingPayments/payment-methods/qr-code-qris/payment-link-view.webp new file mode 100644 index 00000000000..d105bd55f97 Binary files /dev/null and b/source/images/acceptingPayments/payment-methods/qr-code-qris/payment-link-view.webp differ diff --git a/source/images/acceptingPayments/qris-aggregator/qris-flow.webp b/source/images/acceptingPayments/qris-aggregator/qris-flow.webp new file mode 100644 index 00000000000..94a05245a26 Binary files /dev/null and b/source/images/acceptingPayments/qris-aggregator/qris-flow.webp differ diff --git a/source/images/acceptingPayments/va-aggregator/payment-flow.webp b/source/images/acceptingPayments/va-aggregator/payment-flow.webp new file mode 100644 index 00000000000..f7d9176b46b Binary files /dev/null and b/source/images/acceptingPayments/va-aggregator/payment-flow.webp differ diff --git a/source/images/acceptingPayments/va-aggregator/use-cases.webp b/source/images/acceptingPayments/va-aggregator/use-cases.webp new file mode 100644 index 00000000000..ae8e1e38b12 Binary files /dev/null and b/source/images/acceptingPayments/va-aggregator/use-cases.webp differ diff --git a/source/images/acceptingPayments/va-aggregator/viewing-list-of-created-va.webp b/source/images/acceptingPayments/va-aggregator/viewing-list-of-created-va.webp new file mode 100644 index 00000000000..771985a92c9 Binary files /dev/null and b/source/images/acceptingPayments/va-aggregator/viewing-list-of-created-va.webp differ diff --git a/source/images/acceptingPayments/va-aggregator/viewing-list-of-incoming-payment.webp b/source/images/acceptingPayments/va-aggregator/viewing-list-of-incoming-payment.webp new file mode 100644 index 00000000000..a38f35b601c Binary files /dev/null and b/source/images/acceptingPayments/va-aggregator/viewing-list-of-incoming-payment.webp differ diff --git a/source/images/accountPayable/add_vendor_1.png b/source/images/accountPayable/add_vendor_1.png new file mode 100644 index 00000000000..750cabe4716 Binary files /dev/null and b/source/images/accountPayable/add_vendor_1.png differ diff --git a/source/images/accountPayable/add_vendor_2.png b/source/images/accountPayable/add_vendor_2.png new file mode 100644 index 00000000000..d33235636c0 Binary files /dev/null and b/source/images/accountPayable/add_vendor_2.png differ diff --git a/source/images/accountPayable/add_vendor_3.png b/source/images/accountPayable/add_vendor_3.png new file mode 100644 index 00000000000..6178e765e40 Binary files /dev/null and b/source/images/accountPayable/add_vendor_3.png differ diff --git a/source/images/accountPayable/autofilled_idr.png b/source/images/accountPayable/autofilled_idr.png new file mode 100644 index 00000000000..b84721bd9ee Binary files /dev/null and b/source/images/accountPayable/autofilled_idr.png differ diff --git a/source/images/accountPayable/creation_fx.png b/source/images/accountPayable/creation_fx.png new file mode 100644 index 00000000000..fdd833f0c57 Binary files /dev/null and b/source/images/accountPayable/creation_fx.png differ diff --git a/source/images/accountPayable/creation_manual_idr.png b/source/images/accountPayable/creation_manual_idr.png new file mode 100644 index 00000000000..a6225041261 Binary files /dev/null and b/source/images/accountPayable/creation_manual_idr.png differ diff --git a/source/images/accountPayable/creation_ocr_idr.png b/source/images/accountPayable/creation_ocr_idr.png new file mode 100644 index 00000000000..c00c8b561f8 Binary files /dev/null and b/source/images/accountPayable/creation_ocr_idr.png differ diff --git a/source/images/accountPayable/invoice_list.png b/source/images/accountPayable/invoice_list.png new file mode 100644 index 00000000000..edb48880d67 Binary files /dev/null and b/source/images/accountPayable/invoice_list.png differ diff --git a/source/images/accountPayable/multi_account_management.png b/source/images/accountPayable/multi_account_management.png new file mode 100644 index 00000000000..0f0a444d455 Binary files /dev/null and b/source/images/accountPayable/multi_account_management.png differ diff --git a/source/images/accountPayable/payment_option.png b/source/images/accountPayable/payment_option.png new file mode 100644 index 00000000000..f1248dddfd1 Binary files /dev/null and b/source/images/accountPayable/payment_option.png differ diff --git a/source/images/accountPayable/receipt_business_creation_fx.png b/source/images/accountPayable/receipt_business_creation_fx.png new file mode 100644 index 00000000000..507f86613bf Binary files /dev/null and b/source/images/accountPayable/receipt_business_creation_fx.png differ diff --git a/source/images/accountPayable/receipt_individual_creation_fx.png b/source/images/accountPayable/receipt_individual_creation_fx.png new file mode 100644 index 00000000000..3f9466cd635 Binary files /dev/null and b/source/images/accountPayable/receipt_individual_creation_fx.png differ diff --git a/source/images/accountPayable/sender_business_creation_fx.png b/source/images/accountPayable/sender_business_creation_fx.png new file mode 100644 index 00000000000..af47e4a69e0 Binary files /dev/null and b/source/images/accountPayable/sender_business_creation_fx.png differ diff --git a/source/images/accountPayable/sender_individual_creation_fx.png b/source/images/accountPayable/sender_individual_creation_fx.png new file mode 100644 index 00000000000..55cbdb481f5 Binary files /dev/null and b/source/images/accountPayable/sender_individual_creation_fx.png differ diff --git a/source/images/accountPayable/summary_fx.jpg b/source/images/accountPayable/summary_fx.jpg new file mode 100644 index 00000000000..3efab289e5c Binary files /dev/null and b/source/images/accountPayable/summary_fx.jpg differ diff --git a/source/images/accountPayable/supporting_docs_fx.jpg b/source/images/accountPayable/supporting_docs_fx.jpg new file mode 100644 index 00000000000..3dcaa52eb7c Binary files /dev/null and b/source/images/accountPayable/supporting_docs_fx.jpg differ diff --git a/source/images/accountPayable/vendor_details.jpg b/source/images/accountPayable/vendor_details.jpg new file mode 100644 index 00000000000..704fb097c24 Binary files /dev/null and b/source/images/accountPayable/vendor_details.jpg differ diff --git a/source/images/accountPayable/vendor_list.jpg b/source/images/accountPayable/vendor_list.jpg new file mode 100644 index 00000000000..2c5f9fdb6fb Binary files /dev/null and b/source/images/accountPayable/vendor_list.jpg differ diff --git a/source/images/accountReceivable/add_column.png b/source/images/accountReceivable/add_column.png new file mode 100644 index 00000000000..9e813fa4b07 Binary files /dev/null and b/source/images/accountReceivable/add_column.png differ diff --git a/source/images/accountReceivable/add_cust_ar_creation.png b/source/images/accountReceivable/add_cust_ar_creation.png new file mode 100644 index 00000000000..7d925c1b239 Binary files /dev/null and b/source/images/accountReceivable/add_cust_ar_creation.png differ diff --git a/source/images/accountReceivable/add_cust_ar_creation_step1.png b/source/images/accountReceivable/add_cust_ar_creation_step1.png new file mode 100644 index 00000000000..f136e379df1 Binary files /dev/null and b/source/images/accountReceivable/add_cust_ar_creation_step1.png differ diff --git a/source/images/accountReceivable/add_cust_ar_creation_step2.png b/source/images/accountReceivable/add_cust_ar_creation_step2.png new file mode 100644 index 00000000000..38d1f3c5019 Binary files /dev/null and b/source/images/accountReceivable/add_cust_ar_creation_step2.png differ diff --git a/source/images/accountReceivable/add_cust_step1.png b/source/images/accountReceivable/add_cust_step1.png new file mode 100644 index 00000000000..79b1d9a4f1c Binary files /dev/null and b/source/images/accountReceivable/add_cust_step1.png differ diff --git a/source/images/accountReceivable/add_cust_step2.png b/source/images/accountReceivable/add_cust_step2.png new file mode 100644 index 00000000000..38b9e110757 Binary files /dev/null and b/source/images/accountReceivable/add_cust_step2.png differ diff --git a/source/images/accountReceivable/ar_detail_data_sidemodal.png b/source/images/accountReceivable/ar_detail_data_sidemodal.png new file mode 100644 index 00000000000..1afff6b9162 Binary files /dev/null and b/source/images/accountReceivable/ar_detail_data_sidemodal.png differ diff --git a/source/images/accountReceivable/ar_table_data.png b/source/images/accountReceivable/ar_table_data.png new file mode 100644 index 00000000000..26168ee7188 Binary files /dev/null and b/source/images/accountReceivable/ar_table_data.png differ diff --git a/source/images/accountReceivable/ar_table_data_config.png b/source/images/accountReceivable/ar_table_data_config.png new file mode 100644 index 00000000000..22c284d7245 Binary files /dev/null and b/source/images/accountReceivable/ar_table_data_config.png differ diff --git a/source/images/accountReceivable/config_inv_no.png b/source/images/accountReceivable/config_inv_no.png new file mode 100644 index 00000000000..c1f811deef5 Binary files /dev/null and b/source/images/accountReceivable/config_inv_no.png differ diff --git a/source/images/accountReceivable/config_set_appearance.png b/source/images/accountReceivable/config_set_appearance.png new file mode 100644 index 00000000000..470c33e5120 Binary files /dev/null and b/source/images/accountReceivable/config_set_appearance.png differ diff --git a/source/images/accountReceivable/cust_mgmt_table.png b/source/images/accountReceivable/cust_mgmt_table.png new file mode 100644 index 00000000000..0266ec818d3 Binary files /dev/null and b/source/images/accountReceivable/cust_mgmt_table.png differ diff --git a/source/images/accountReceivable/detail_cust_data.png b/source/images/accountReceivable/detail_cust_data.png new file mode 100644 index 00000000000..963534f40e8 Binary files /dev/null and b/source/images/accountReceivable/detail_cust_data.png differ diff --git a/source/images/accountReceivable/detail_cust_trx.png b/source/images/accountReceivable/detail_cust_trx.png new file mode 100644 index 00000000000..ab0fe3325c3 Binary files /dev/null and b/source/images/accountReceivable/detail_cust_trx.png differ diff --git a/source/images/accountReceivable/detail_invoice_data.png b/source/images/accountReceivable/detail_invoice_data.png new file mode 100644 index 00000000000..e6dabccb872 Binary files /dev/null and b/source/images/accountReceivable/detail_invoice_data.png differ diff --git a/source/images/accountReceivable/invoice_creation_page.png b/source/images/accountReceivable/invoice_creation_page.png new file mode 100644 index 00000000000..95f8c816297 Binary files /dev/null and b/source/images/accountReceivable/invoice_creation_page.png differ diff --git a/source/images/accountReceivable/invoice_preview.png b/source/images/accountReceivable/invoice_preview.png new file mode 100644 index 00000000000..7d0eeb93c32 Binary files /dev/null and b/source/images/accountReceivable/invoice_preview.png differ diff --git a/source/images/accountReceivable/invoice_template_configuration_page.png b/source/images/accountReceivable/invoice_template_configuration_page.png new file mode 100644 index 00000000000..6f233d974e8 Binary files /dev/null and b/source/images/accountReceivable/invoice_template_configuration_page.png differ diff --git a/source/images/accountReceivable/payment_link_config.png b/source/images/accountReceivable/payment_link_config.png new file mode 100644 index 00000000000..013ae9bbbc3 Binary files /dev/null and b/source/images/accountReceivable/payment_link_config.png differ diff --git a/source/images/accountReceivable/send_wa_creation.png b/source/images/accountReceivable/send_wa_creation.png new file mode 100644 index 00000000000..29b0ca88ee7 Binary files /dev/null and b/source/images/accountReceivable/send_wa_creation.png differ diff --git a/source/images/accountReceivable/send_wa_detail.png b/source/images/accountReceivable/send_wa_detail.png new file mode 100644 index 00000000000..21582d4ef08 Binary files /dev/null and b/source/images/accountReceivable/send_wa_detail.png differ diff --git a/source/images/accountReceivable/send_wa_table.png b/source/images/accountReceivable/send_wa_table.png new file mode 100644 index 00000000000..919a7cd31aa Binary files /dev/null and b/source/images/accountReceivable/send_wa_table.png differ diff --git a/source/images/activating_account.png b/source/images/activating_account.png new file mode 100644 index 00000000000..e32389c9eb9 Binary files /dev/null and b/source/images/activating_account.png differ diff --git a/source/images/add_column_amount_customization.png b/source/images/add_column_amount_customization.png new file mode 100644 index 00000000000..03d091feb0a Binary files /dev/null and b/source/images/add_column_amount_customization.png differ diff --git a/source/images/add_new_customer.png b/source/images/add_new_customer.png new file mode 100644 index 00000000000..1f30f09c950 Binary files /dev/null and b/source/images/add_new_customer.png differ diff --git a/source/images/after_state_amount_customization.png b/source/images/after_state_amount_customization.png new file mode 100644 index 00000000000..22610b47a89 Binary files /dev/null and b/source/images/after_state_amount_customization.png differ diff --git a/source/images/api_disburse_error_reason.png b/source/images/api_disburse_error_reason.png new file mode 100644 index 00000000000..91be5af190c Binary files /dev/null and b/source/images/api_disburse_error_reason.png differ diff --git a/source/images/api_disburse_list.png b/source/images/api_disburse_list.png new file mode 100644 index 00000000000..33c61d39b83 Binary files /dev/null and b/source/images/api_disburse_list.png differ diff --git a/source/images/api_disburse_resend_callback.png b/source/images/api_disburse_resend_callback.png new file mode 100644 index 00000000000..464a96e46e7 Binary files /dev/null and b/source/images/api_disburse_resend_callback.png differ diff --git a/source/images/api_disburse_retry_callback_developer_option.png b/source/images/api_disburse_retry_callback_developer_option.png new file mode 100644 index 00000000000..06bd28a4442 Binary files /dev/null and b/source/images/api_disburse_retry_callback_developer_option.png differ diff --git a/source/images/api_disburse_success.png b/source/images/api_disburse_success.png new file mode 100644 index 00000000000..434203c89c9 Binary files /dev/null and b/source/images/api_disburse_success.png differ diff --git a/source/images/arrow-bottom.svg b/source/images/arrow-bottom.svg new file mode 100644 index 00000000000..a2d2a2dfb7b --- /dev/null +++ b/source/images/arrow-bottom.svg @@ -0,0 +1,3 @@ + + + diff --git a/source/images/arrow-right.svg b/source/images/arrow-right.svg new file mode 100644 index 00000000000..f3c31dae367 --- /dev/null +++ b/source/images/arrow-right.svg @@ -0,0 +1,3 @@ + + + diff --git a/source/images/auto_withdrawal.png b/source/images/auto_withdrawal.png new file mode 100644 index 00000000000..895d822b676 Binary files /dev/null and b/source/images/auto_withdrawal.png differ diff --git a/source/images/bank_transfer_callback_va.png b/source/images/bank_transfer_callback_va.png new file mode 100644 index 00000000000..4af94fa41da Binary files /dev/null and b/source/images/bank_transfer_callback_va.png differ diff --git a/source/images/beranda.svg b/source/images/beranda.svg new file mode 100644 index 00000000000..7a5452214d9 --- /dev/null +++ b/source/images/beranda.svg @@ -0,0 +1,4 @@ + + + + diff --git a/source/images/bulk_disburse_1.png b/source/images/bulk_disburse_1.png new file mode 100644 index 00000000000..81c714d630c Binary files /dev/null and b/source/images/bulk_disburse_1.png differ diff --git a/source/images/bulk_disburse_2.png b/source/images/bulk_disburse_2.png new file mode 100644 index 00000000000..c07157fd61b Binary files /dev/null and b/source/images/bulk_disburse_2.png differ diff --git a/source/images/bulk_disburse_3.png b/source/images/bulk_disburse_3.png new file mode 100644 index 00000000000..39fd05d6713 Binary files /dev/null and b/source/images/bulk_disburse_3.png differ diff --git a/source/images/bulk_disburse_4.png b/source/images/bulk_disburse_4.png new file mode 100644 index 00000000000..c0186b54a56 Binary files /dev/null and b/source/images/bulk_disburse_4.png differ diff --git a/source/images/bulk_disburse_5.png b/source/images/bulk_disburse_5.png new file mode 100644 index 00000000000..d96308f35bb Binary files /dev/null and b/source/images/bulk_disburse_5.png differ diff --git a/source/images/bulk_disburse_cancel.png b/source/images/bulk_disburse_cancel.png new file mode 100644 index 00000000000..c9b03fc7fc8 Binary files /dev/null and b/source/images/bulk_disburse_cancel.png differ diff --git a/source/images/bulk_disburse_error_reason.png b/source/images/bulk_disburse_error_reason.png new file mode 100644 index 00000000000..678670ef860 Binary files /dev/null and b/source/images/bulk_disburse_error_reason.png differ diff --git a/source/images/bulk_disburse_finish.png b/source/images/bulk_disburse_finish.png new file mode 100644 index 00000000000..09a033df104 Binary files /dev/null and b/source/images/bulk_disburse_finish.png differ diff --git a/source/images/bulk_disburse_in_progress.png b/source/images/bulk_disburse_in_progress.png new file mode 100644 index 00000000000..1a915976be8 Binary files /dev/null and b/source/images/bulk_disburse_in_progress.png differ diff --git a/source/images/bulk_disburse_list_incomplete.png b/source/images/bulk_disburse_list_incomplete.png new file mode 100644 index 00000000000..876e9124bd1 Binary files /dev/null and b/source/images/bulk_disburse_list_incomplete.png differ diff --git a/source/images/claim-fund-create-success.png b/source/images/claim-fund-create-success.png new file mode 100644 index 00000000000..b3d6fee7fae Binary files /dev/null and b/source/images/claim-fund-create-success.png differ diff --git a/source/images/claim-fund-create.png b/source/images/claim-fund-create.png new file mode 100644 index 00000000000..fc90e8e7dfc Binary files /dev/null and b/source/images/claim-fund-create.png differ diff --git a/source/images/claim-fund-flow.png b/source/images/claim-fund-flow.png new file mode 100644 index 00000000000..38197c957b7 Binary files /dev/null and b/source/images/claim-fund-flow.png differ diff --git a/source/images/claim-fund-input-detail.png b/source/images/claim-fund-input-detail.png new file mode 100644 index 00000000000..3e0ade9fa6a Binary files /dev/null and b/source/images/claim-fund-input-detail.png differ diff --git a/source/images/claim-fund-input-submitted.png b/source/images/claim-fund-input-submitted.png new file mode 100644 index 00000000000..6741f524557 Binary files /dev/null and b/source/images/claim-fund-input-submitted.png differ diff --git a/source/images/claim-fund-landing.png b/source/images/claim-fund-landing.png new file mode 100644 index 00000000000..de75c166d36 Binary files /dev/null and b/source/images/claim-fund-landing.png differ diff --git a/source/images/claim-fund-partner-approval1.png b/source/images/claim-fund-partner-approval1.png new file mode 100644 index 00000000000..75b6992de89 Binary files /dev/null and b/source/images/claim-fund-partner-approval1.png differ diff --git a/source/images/claim-fund-partner-approval2.png b/source/images/claim-fund-partner-approval2.png new file mode 100644 index 00000000000..bfd56b74a14 Binary files /dev/null and b/source/images/claim-fund-partner-approval2.png differ diff --git a/source/images/claim-fund-user-email.png b/source/images/claim-fund-user-email.png new file mode 100644 index 00000000000..743b72f9331 Binary files /dev/null and b/source/images/claim-fund-user-email.png differ diff --git a/source/images/claim-fund-user-success.png b/source/images/claim-fund-user-success.png new file mode 100644 index 00000000000..2f758a37ba4 Binary files /dev/null and b/source/images/claim-fund-user-success.png differ diff --git a/source/images/close.svg b/source/images/close.svg new file mode 100644 index 00000000000..90cbff08273 --- /dev/null +++ b/source/images/close.svg @@ -0,0 +1,3 @@ + + + diff --git a/source/images/column_addition_substraction.png b/source/images/column_addition_substraction.png new file mode 100644 index 00000000000..18bec3bb136 Binary files /dev/null and b/source/images/column_addition_substraction.png differ diff --git a/source/images/create_new_invoice_1.png b/source/images/create_new_invoice_1.png new file mode 100644 index 00000000000..836743ecf99 Binary files /dev/null and b/source/images/create_new_invoice_1.png differ diff --git a/source/images/create_new_invoice_2.png b/source/images/create_new_invoice_2.png new file mode 100644 index 00000000000..46286cd6faf Binary files /dev/null and b/source/images/create_new_invoice_2.png differ diff --git a/source/images/create_payment_link_1.png b/source/images/create_payment_link_1.png new file mode 100644 index 00000000000..d103e53f041 Binary files /dev/null and b/source/images/create_payment_link_1.png differ diff --git a/source/images/create_payment_link_2.png b/source/images/create_payment_link_2.png new file mode 100644 index 00000000000..a109ff4bae8 Binary files /dev/null and b/source/images/create_payment_link_2.png differ diff --git a/source/images/create_payment_link_3.png b/source/images/create_payment_link_3.png new file mode 100644 index 00000000000..e4be6d2764d Binary files /dev/null and b/source/images/create_payment_link_3.png differ diff --git a/source/images/create_payment_link_4.png b/source/images/create_payment_link_4.png new file mode 100644 index 00000000000..8d99c78a528 Binary files /dev/null and b/source/images/create_payment_link_4.png differ diff --git a/source/images/create_payment_link_5.png b/source/images/create_payment_link_5.png new file mode 100644 index 00000000000..46447220b44 Binary files /dev/null and b/source/images/create_payment_link_5.png differ diff --git a/source/images/create_payment_link_6.png b/source/images/create_payment_link_6.png new file mode 100644 index 00000000000..89d50ccf43a Binary files /dev/null and b/source/images/create_payment_link_6.png differ diff --git a/source/images/create_reusable_1.png b/source/images/create_reusable_1.png new file mode 100644 index 00000000000..623d5e534fa Binary files /dev/null and b/source/images/create_reusable_1.png differ diff --git a/source/images/create_reusable_2.png b/source/images/create_reusable_2.png new file mode 100644 index 00000000000..5308d3e7652 Binary files /dev/null and b/source/images/create_reusable_2.png differ diff --git a/source/images/create_reusable_3.png b/source/images/create_reusable_3.png new file mode 100644 index 00000000000..e92b975fee0 Binary files /dev/null and b/source/images/create_reusable_3.png differ diff --git a/source/images/creating_account_1.jpg b/source/images/creating_account_1.jpg new file mode 100644 index 00000000000..64bfdcea116 Binary files /dev/null and b/source/images/creating_account_1.jpg differ diff --git a/source/images/creating_account_1.png b/source/images/creating_account_1.png new file mode 100644 index 00000000000..aabb10c7794 Binary files /dev/null and b/source/images/creating_account_1.png differ diff --git a/source/images/creating_account_2.jpg b/source/images/creating_account_2.jpg new file mode 100644 index 00000000000..a9b216b6f4b Binary files /dev/null and b/source/images/creating_account_2.jpg differ diff --git a/source/images/creating_account_2.png b/source/images/creating_account_2.png new file mode 100644 index 00000000000..e17a00ad65b Binary files /dev/null and b/source/images/creating_account_2.png differ diff --git a/source/images/customer_management_add_customer.png b/source/images/customer_management_add_customer.png new file mode 100644 index 00000000000..c5b86017819 Binary files /dev/null and b/source/images/customer_management_add_customer.png differ diff --git a/source/images/customer_management_add_customer_1.png b/source/images/customer_management_add_customer_1.png new file mode 100644 index 00000000000..60577bda47b Binary files /dev/null and b/source/images/customer_management_add_customer_1.png differ diff --git a/source/images/customer_management_detail.png b/source/images/customer_management_detail.png new file mode 100644 index 00000000000..a609d5a763b Binary files /dev/null and b/source/images/customer_management_detail.png differ diff --git a/source/images/customer_management_home.png b/source/images/customer_management_home.png new file mode 100644 index 00000000000..b21aedb103f Binary files /dev/null and b/source/images/customer_management_home.png differ diff --git a/source/images/desktop_accountingapp_bulkdisbursement.png b/source/images/desktop_accountingapp_bulkdisbursement.png new file mode 100644 index 00000000000..32b6cb4509d Binary files /dev/null and b/source/images/desktop_accountingapp_bulkdisbursement.png differ diff --git a/source/images/desktop_accurate_aplikasisaya.png b/source/images/desktop_accurate_aplikasisaya.png new file mode 100644 index 00000000000..da58d6d59a5 Binary files /dev/null and b/source/images/desktop_accurate_aplikasisaya.png differ diff --git a/source/images/desktop_accurate_authorize.png b/source/images/desktop_accurate_authorize.png new file mode 100644 index 00000000000..5f85ae1c518 Binary files /dev/null and b/source/images/desktop_accurate_authorize.png differ diff --git a/source/images/desktop_accurate_config_coa.png b/source/images/desktop_accurate_config_coa.png new file mode 100644 index 00000000000..ec0df8f6305 Binary files /dev/null and b/source/images/desktop_accurate_config_coa.png differ diff --git a/source/images/desktop_accurate_connect.png b/source/images/desktop_accurate_connect.png new file mode 100644 index 00000000000..c0648ffe7ab Binary files /dev/null and b/source/images/desktop_accurate_connect.png differ diff --git a/source/images/desktop_accurate_connected_status.png b/source/images/desktop_accurate_connected_status.png new file mode 100644 index 00000000000..52735544620 Binary files /dev/null and b/source/images/desktop_accurate_connected_status.png differ diff --git a/source/images/desktop_accurate_listing.png b/source/images/desktop_accurate_listing.png new file mode 100644 index 00000000000..8184daff274 Binary files /dev/null and b/source/images/desktop_accurate_listing.png differ diff --git a/source/images/desktop_accurate_login.png b/source/images/desktop_accurate_login.png new file mode 100644 index 00000000000..a98783c2075 Binary files /dev/null and b/source/images/desktop_accurate_login.png differ diff --git a/source/images/desktop_accurate_select_database.png b/source/images/desktop_accurate_select_database.png new file mode 100644 index 00000000000..04073b5304f Binary files /dev/null and b/source/images/desktop_accurate_select_database.png differ diff --git a/source/images/desktop_accurate_success.png b/source/images/desktop_accurate_success.png new file mode 100644 index 00000000000..f336d3afdd6 Binary files /dev/null and b/source/images/desktop_accurate_success.png differ diff --git a/source/images/desktop_analytics_paymethod.png b/source/images/desktop_analytics_paymethod.png new file mode 100644 index 00000000000..9f7cd02c116 Binary files /dev/null and b/source/images/desktop_analytics_paymethod.png differ diff --git a/source/images/desktop_analytics_spend_earn.png b/source/images/desktop_analytics_spend_earn.png new file mode 100644 index 00000000000..2e28a70f0c5 Binary files /dev/null and b/source/images/desktop_analytics_spend_earn.png differ diff --git a/source/images/desktop_analytics_spendingearning.png b/source/images/desktop_analytics_spendingearning.png new file mode 100644 index 00000000000..73da5dbbf69 Binary files /dev/null and b/source/images/desktop_analytics_spendingearning.png differ diff --git a/source/images/desktop_bcauniquecode_input_amount.png b/source/images/desktop_bcauniquecode_input_amount.png new file mode 100644 index 00000000000..e6f14020cdd Binary files /dev/null and b/source/images/desktop_bcauniquecode_input_amount.png differ diff --git a/source/images/desktop_bcauniquecode_success.png b/source/images/desktop_bcauniquecode_success.png new file mode 100644 index 00000000000..7db8d13940a Binary files /dev/null and b/source/images/desktop_bcauniquecode_success.png differ diff --git a/source/images/desktop_bcauniquecode_transfer.png b/source/images/desktop_bcauniquecode_transfer.png new file mode 100644 index 00000000000..8ab03441fe2 Binary files /dev/null and b/source/images/desktop_bcauniquecode_transfer.png differ diff --git a/source/images/desktop_disbursement_child_balance.png b/source/images/desktop_disbursement_child_balance.png new file mode 100644 index 00000000000..f8232b80881 Binary files /dev/null and b/source/images/desktop_disbursement_child_balance.png differ diff --git a/source/images/desktop_disbursement_child_balance_2.png b/source/images/desktop_disbursement_child_balance_2.png new file mode 100644 index 00000000000..a9f300c6931 Binary files /dev/null and b/source/images/desktop_disbursement_child_balance_2.png differ diff --git a/source/images/desktop_login.png b/source/images/desktop_login.png new file mode 100644 index 00000000000..db9ceef3d74 Binary files /dev/null and b/source/images/desktop_login.png differ diff --git a/source/images/desktop_login_otp.png b/source/images/desktop_login_otp.png new file mode 100644 index 00000000000..955b6befade Binary files /dev/null and b/source/images/desktop_login_otp.png differ diff --git a/source/images/desktop_register.png b/source/images/desktop_register.png new file mode 100644 index 00000000000..6884a1e9207 Binary files /dev/null and b/source/images/desktop_register.png differ diff --git a/source/images/desktop_topup.png b/source/images/desktop_topup.png new file mode 100644 index 00000000000..e2b7b158dc1 Binary files /dev/null and b/source/images/desktop_topup.png differ diff --git a/source/images/email_otp.png b/source/images/email_otp.png new file mode 100644 index 00000000000..2273c163679 Binary files /dev/null and b/source/images/email_otp.png differ diff --git a/source/images/ewallet_dashboard.png b/source/images/ewallet_dashboard.png new file mode 100644 index 00000000000..d5f4b654d31 Binary files /dev/null and b/source/images/ewallet_dashboard.png differ diff --git a/source/images/ewallet_product_flow.png b/source/images/ewallet_product_flow.png new file mode 100644 index 00000000000..d84ea16c1fd Binary files /dev/null and b/source/images/ewallet_product_flow.png differ diff --git a/source/images/ewallet_refund_account_statement.png b/source/images/ewallet_refund_account_statement.png new file mode 100644 index 00000000000..66377e47013 Binary files /dev/null and b/source/images/ewallet_refund_account_statement.png differ diff --git a/source/images/ewallet_refund_action.png b/source/images/ewallet_refund_action.png new file mode 100644 index 00000000000..a61c18ef122 Binary files /dev/null and b/source/images/ewallet_refund_action.png differ diff --git a/source/images/ewallet_refund_amount.png b/source/images/ewallet_refund_amount.png new file mode 100644 index 00000000000..8201f066f43 Binary files /dev/null and b/source/images/ewallet_refund_amount.png differ diff --git a/source/images/ewallet_refund_balance.png b/source/images/ewallet_refund_balance.png new file mode 100644 index 00000000000..60197c4c277 Binary files /dev/null and b/source/images/ewallet_refund_balance.png differ diff --git a/source/images/ewallet_refund_operational.png b/source/images/ewallet_refund_operational.png new file mode 100644 index 00000000000..9fc6bca0f57 Binary files /dev/null and b/source/images/ewallet_refund_operational.png differ diff --git a/source/images/ewallet_resend_callback.png b/source/images/ewallet_resend_callback.png new file mode 100644 index 00000000000..78c3d726e23 Binary files /dev/null and b/source/images/ewallet_resend_callback.png differ diff --git a/source/images/ewallet_testing.png b/source/images/ewallet_testing.png new file mode 100644 index 00000000000..8d5d8d21a0b Binary files /dev/null and b/source/images/ewallet_testing.png differ diff --git a/source/images/ewallet_testing_2.png b/source/images/ewallet_testing_2.png new file mode 100644 index 00000000000..b91251a5f7b Binary files /dev/null and b/source/images/ewallet_testing_2.png differ diff --git a/source/images/expense-management.svg b/source/images/expense-management.svg new file mode 100644 index 00000000000..7218e7268bc --- /dev/null +++ b/source/images/expense-management.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/source/images/faqs.svg b/source/images/faqs.svg new file mode 100644 index 00000000000..427c7688e83 --- /dev/null +++ b/source/images/faqs.svg @@ -0,0 +1,3 @@ + + + diff --git a/source/images/favicon.ico b/source/images/favicon.ico new file mode 100644 index 00000000000..c2b09bca606 Binary files /dev/null and b/source/images/favicon.ico differ diff --git a/source/images/flow_whatsapp_notif.png b/source/images/flow_whatsapp_notif.png new file mode 100644 index 00000000000..ea003dc9455 Binary files /dev/null and b/source/images/flow_whatsapp_notif.png differ diff --git a/source/images/home.svg b/source/images/home.svg new file mode 100644 index 00000000000..7a5452214d9 --- /dev/null +++ b/source/images/home.svg @@ -0,0 +1,4 @@ + + + + diff --git a/source/images/how_oy_can_help.png b/source/images/how_oy_can_help.png new file mode 100644 index 00000000000..5ce2e374ae3 Binary files /dev/null and b/source/images/how_oy_can_help.png differ diff --git a/source/images/img_integration.png b/source/images/img_integration.png new file mode 100644 index 00000000000..7365c973dcf Binary files /dev/null and b/source/images/img_integration.png differ diff --git a/source/images/img_signup.png b/source/images/img_signup.png new file mode 100644 index 00000000000..9d08418c298 Binary files /dev/null and b/source/images/img_signup.png differ diff --git a/source/images/internationalTransfer/account_selected.png b/source/images/internationalTransfer/account_selected.png new file mode 100644 index 00000000000..c8ea8972a63 Binary files /dev/null and b/source/images/internationalTransfer/account_selected.png differ diff --git a/source/images/internationalTransfer/approval_default_disabled.png b/source/images/internationalTransfer/approval_default_disabled.png new file mode 100644 index 00000000000..e1e886e03a2 Binary files /dev/null and b/source/images/internationalTransfer/approval_default_disabled.png differ diff --git a/source/images/internationalTransfer/approver_decision.png b/source/images/internationalTransfer/approver_decision.png new file mode 100644 index 00000000000..c87e58343db Binary files /dev/null and b/source/images/internationalTransfer/approver_decision.png differ diff --git a/source/images/internationalTransfer/approver_email.png b/source/images/internationalTransfer/approver_email.png new file mode 100644 index 00000000000..c87e58343db Binary files /dev/null and b/source/images/internationalTransfer/approver_email.png differ diff --git a/source/images/internationalTransfer/approver_selection.png b/source/images/internationalTransfer/approver_selection.png new file mode 100644 index 00000000000..a6a63a46624 Binary files /dev/null and b/source/images/internationalTransfer/approver_selection.png differ diff --git a/source/images/internationalTransfer/balance_less_than_send_amount.jpg b/source/images/internationalTransfer/balance_less_than_send_amount.jpg new file mode 100644 index 00000000000..d2ed7ae8b66 Binary files /dev/null and b/source/images/internationalTransfer/balance_less_than_send_amount.jpg differ diff --git a/source/images/internationalTransfer/balance_not_enough.png b/source/images/internationalTransfer/balance_not_enough.png new file mode 100644 index 00000000000..7ca22a7340a Binary files /dev/null and b/source/images/internationalTransfer/balance_not_enough.png differ diff --git a/source/images/internationalTransfer/create_inter_remit.jpg b/source/images/internationalTransfer/create_inter_remit.jpg new file mode 100644 index 00000000000..4ae252afc64 Binary files /dev/null and b/source/images/internationalTransfer/create_inter_remit.jpg differ diff --git a/source/images/internationalTransfer/failed_email.png b/source/images/internationalTransfer/failed_email.png new file mode 100644 index 00000000000..f773d692962 Binary files /dev/null and b/source/images/internationalTransfer/failed_email.png differ diff --git a/source/images/internationalTransfer/in_progress_transaction.png b/source/images/internationalTransfer/in_progress_transaction.png new file mode 100644 index 00000000000..0820dcb8e6b Binary files /dev/null and b/source/images/internationalTransfer/in_progress_transaction.png differ diff --git a/source/images/internationalTransfer/incorrect_password.png b/source/images/internationalTransfer/incorrect_password.png new file mode 100644 index 00000000000..d22212a3fd4 Binary files /dev/null and b/source/images/internationalTransfer/incorrect_password.png differ diff --git a/source/images/internationalTransfer/input_amount.jpg b/source/images/internationalTransfer/input_amount.jpg new file mode 100644 index 00000000000..51ed0e4f3c3 Binary files /dev/null and b/source/images/internationalTransfer/input_amount.jpg differ diff --git a/source/images/internationalTransfer/input_business_recipient.jpg b/source/images/internationalTransfer/input_business_recipient.jpg new file mode 100644 index 00000000000..b2975de576b Binary files /dev/null and b/source/images/internationalTransfer/input_business_recipient.jpg differ diff --git a/source/images/internationalTransfer/input_business_sender.jpg b/source/images/internationalTransfer/input_business_sender.jpg new file mode 100644 index 00000000000..81626ba7ef7 Binary files /dev/null and b/source/images/internationalTransfer/input_business_sender.jpg differ diff --git a/source/images/internationalTransfer/input_individual_recipient.jpg b/source/images/internationalTransfer/input_individual_recipient.jpg new file mode 100644 index 00000000000..dfc5cced26c Binary files /dev/null and b/source/images/internationalTransfer/input_individual_recipient.jpg differ diff --git a/source/images/internationalTransfer/input_individual_sender.jpg b/source/images/internationalTransfer/input_individual_sender.jpg new file mode 100644 index 00000000000..65bf159a0b1 Binary files /dev/null and b/source/images/internationalTransfer/input_individual_sender.jpg differ diff --git a/source/images/internationalTransfer/list_of_existing_recipients.jpg b/source/images/internationalTransfer/list_of_existing_recipients.jpg new file mode 100644 index 00000000000..954b2ff7919 Binary files /dev/null and b/source/images/internationalTransfer/list_of_existing_recipients.jpg differ diff --git a/source/images/internationalTransfer/list_of_transactions.png b/source/images/internationalTransfer/list_of_transactions.png new file mode 100644 index 00000000000..751c1124a6a Binary files /dev/null and b/source/images/internationalTransfer/list_of_transactions.png differ diff --git a/source/images/internationalTransfer/one_approver.png b/source/images/internationalTransfer/one_approver.png new file mode 100644 index 00000000000..e046498b715 Binary files /dev/null and b/source/images/internationalTransfer/one_approver.png differ diff --git a/source/images/internationalTransfer/password_filled.png b/source/images/internationalTransfer/password_filled.png new file mode 100644 index 00000000000..93aef9c94e8 Binary files /dev/null and b/source/images/internationalTransfer/password_filled.png differ diff --git a/source/images/internationalTransfer/success_email.png b/source/images/internationalTransfer/success_email.png new file mode 100644 index 00000000000..ab1a475afce Binary files /dev/null and b/source/images/internationalTransfer/success_email.png differ diff --git a/source/images/internationalTransfer/summary.png b/source/images/internationalTransfer/summary.png new file mode 100644 index 00000000000..21eae8a0824 Binary files /dev/null and b/source/images/internationalTransfer/summary.png differ diff --git a/source/images/internationalTransfer/transaction_detail.png b/source/images/internationalTransfer/transaction_detail.png new file mode 100644 index 00000000000..5c40dce0dcb Binary files /dev/null and b/source/images/internationalTransfer/transaction_detail.png differ diff --git a/source/images/internationalTransfer/transfer_reason_docs.jpg b/source/images/internationalTransfer/transfer_reason_docs.jpg new file mode 100644 index 00000000000..535e61a569f Binary files /dev/null and b/source/images/internationalTransfer/transfer_reason_docs.jpg differ diff --git a/source/images/internationalTransfer/update_exchange_rate.png b/source/images/internationalTransfer/update_exchange_rate.png new file mode 100644 index 00000000000..7cd5922e4d4 Binary files /dev/null and b/source/images/internationalTransfer/update_exchange_rate.png differ diff --git a/source/images/invoice_list.png b/source/images/invoice_list.png new file mode 100644 index 00000000000..b410b4e47da Binary files /dev/null and b/source/images/invoice_list.png differ diff --git a/source/images/jurnal_authorization_page.png b/source/images/jurnal_authorization_page.png new file mode 100644 index 00000000000..c938f9e5d00 Binary files /dev/null and b/source/images/jurnal_authorization_page.png differ diff --git a/source/images/jurnal_bulk_disburse_coa.png b/source/images/jurnal_bulk_disburse_coa.png new file mode 100644 index 00000000000..6e5994770ae Binary files /dev/null and b/source/images/jurnal_bulk_disburse_coa.png differ diff --git a/source/images/jurnal_bulk_disburse_txn.png b/source/images/jurnal_bulk_disburse_txn.png new file mode 100644 index 00000000000..c841b1c07bc Binary files /dev/null and b/source/images/jurnal_bulk_disburse_txn.png differ diff --git a/source/images/jurnal_coa_mapping.png b/source/images/jurnal_coa_mapping.png new file mode 100644 index 00000000000..ba7dddd643b Binary files /dev/null and b/source/images/jurnal_coa_mapping.png differ diff --git a/source/images/jurnal_connect_status.png b/source/images/jurnal_connect_status.png new file mode 100644 index 00000000000..010b8ff69cf Binary files /dev/null and b/source/images/jurnal_connect_status.png differ diff --git a/source/images/jurnal_connection_success.png b/source/images/jurnal_connection_success.png new file mode 100644 index 00000000000..a2be47bcbe5 Binary files /dev/null and b/source/images/jurnal_connection_success.png differ diff --git a/source/images/jurnal_integration_menu.png b/source/images/jurnal_integration_menu.png new file mode 100644 index 00000000000..90709410558 Binary files /dev/null and b/source/images/jurnal_integration_menu.png differ diff --git a/source/images/jurnal_login.png b/source/images/jurnal_login.png new file mode 100644 index 00000000000..a4ea38a0eae Binary files /dev/null and b/source/images/jurnal_login.png differ diff --git a/source/images/jurnal_oy_balance.png b/source/images/jurnal_oy_balance.png new file mode 100644 index 00000000000..10a720b6f83 Binary files /dev/null and b/source/images/jurnal_oy_balance.png differ diff --git a/source/images/jurnal_success_notif.png b/source/images/jurnal_success_notif.png new file mode 100644 index 00000000000..66e947ef75d Binary files /dev/null and b/source/images/jurnal_success_notif.png differ diff --git a/source/images/kirim-uang.svg b/source/images/kirim-uang.svg new file mode 100644 index 00000000000..7c4b5a09728 --- /dev/null +++ b/source/images/kirim-uang.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/source/images/login.png b/source/images/login.png new file mode 100644 index 00000000000..f329681fd61 Binary files /dev/null and b/source/images/login.png differ diff --git a/source/images/logo.png b/source/images/logo.png index fa1f13da819..d00813f672b 100644 Binary files a/source/images/logo.png and b/source/images/logo.png differ diff --git a/source/images/logo.svg b/source/images/logo.svg new file mode 100644 index 00000000000..dd2b5b0d428 --- /dev/null +++ b/source/images/logo.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + Payment Link + \ No newline at end of file diff --git a/source/images/lottie/learn-more.json b/source/images/lottie/learn-more.json new file mode 100644 index 00000000000..0a8b6ef5514 --- /dev/null +++ b/source/images/lottie/learn-more.json @@ -0,0 +1 @@ +{"v":"5.7.14","fr":30,"ip":0,"op":80,"w":150,"h":56,"nm":"Try Demo Button","ddd":0,"assets":[],"fonts":{"list":[{"fName":"DMSans-Medium","fFamily":"DM Sans","fStyle":"Medium","ascent":71.9985961914062}]},"layers":[{"ddd":0,"ind":1,"ty":3,"nm":"Null 1","sr":1,"ks":{"o":{"a":0,"k":0,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[75,28,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[65,65,100],"ix":6,"l":2}},"ao":0,"ip":0,"op":80,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":5,"nm":"Learn More","parent":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,7,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.667,0.667,0.667],"y":[0.905,0.905,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":0,"s":[100,100,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[-0.095,-0.095,0]},"t":30,"s":[116.974,116.974,100]},{"t":60,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"t":{"d":{"k":[{"s":{"s":20,"f":"DMSans-Medium","t":"Learn More","ca":0,"j":2,"tr":-10,"lh":54,"ls":0,"fc":[1,1,1]},"t":0}]},"p":{},"m":{"g":1,"a":{"a":0,"k":[0,0],"ix":2}},"a":[]},"ip":0,"op":80,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"Layer 1","parent":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-15.464,0],[0,0],[0,-15.464],[0,0],[15.464,0],[0,0],[0,15.464],[0,0]],"o":[[0,0],[15.464,0],[0,0],[0,15.464],[0,0],[-15.464,0],[0,0],[0,-15.464]],"v":[[-67,-28],[67,-28],[95,0],[95,0],[67,28],[-67,28],[-95,0],[-95,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.243137254902,0.650980392157,0.607843137255,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":80,"st":0,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"Layer 4","parent":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[86.842,97.485,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-15.464,0],[0,0],[0,-15.464],[0,0],[15.464,0],[0,0],[0,15.464],[0,0]],"o":[[0,0],[15.464,0],[0,0],[0,15.464],[0,0],[-15.464,0],[0,0],[0,-15.464]],"v":[[-67,-28],[67,-28],[95,0],[95,0],[67,28],[-67,28],[-95,0],[-95,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.50445209578,0.835294117647,0.800301226448,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":3,"ix":5},"lc":1,"lj":2,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.67,0.67],"y":[1,1]},"o":{"x":[0.33,0.33],"y":[0,0]},"t":30,"s":[98,82]},{"t":60,"s":[136,151]}],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":1,"k":[{"i":{"x":[0.67],"y":[1]},"o":{"x":[0.33],"y":[0]},"t":30,"s":[100]},{"i":{"x":[0.67],"y":[1]},"o":{"x":[0.33],"y":[0]},"t":45,"s":[50]},{"t":60,"s":[0]}],"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":30,"op":90,"st":30,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"Layer 3","parent":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[86.842,97.485,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-15.464,0],[0,0],[0,-15.464],[0,0],[15.464,0],[0,0],[0,15.464],[0,0]],"o":[[0,0],[15.464,0],[0,0],[0,15.464],[0,0],[-15.464,0],[0,0],[0,-15.464]],"v":[[-67,-28],[67,-28],[95,0],[95,0],[67,28],[-67,28],[-95,0],[-95,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.50445209578,0.835294117647,0.800301226448,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":3,"ix":5},"lc":1,"lj":2,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.67,0.67],"y":[1,1]},"o":{"x":[0.33,0.33],"y":[0,0]},"t":0,"s":[98,82]},{"t":30,"s":[136,151]}],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":1,"k":[{"i":{"x":[0.67],"y":[1]},"o":{"x":[0.33],"y":[0]},"t":0,"s":[100]},{"i":{"x":[0.67],"y":[1]},"o":{"x":[0.33],"y":[0]},"t":15,"s":[50]},{"t":30,"s":[0]}],"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":60,"st":0,"bm":0}],"markers":[],"chars":[{"ch":"L","size":20,"style":"Medium","w":53.8,"data":{"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[49.5,0],[49.5,-8.4],[17.7,-8.4],[17.7,-70],[7.1,-70],[7.1,0]],"c":true},"ix":2},"nm":"L","mn":"ADBE Vector Shape - Group","hd":false}],"nm":"L","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}]},"fFamily":"DM Sans"},{"ch":"e","size":20,"style":"Medium","w":58.4,"data":{"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-3.967,3.134],[-1.334,4.667],[0,0],[2.033,-1.366],[3,0],[2.833,2.5],[0.333,4.8],[0,0],[-0.034,0.767],[0,0.667],[2,3.6],[3.6,2.167],[4.866,0],[3.766,-2.2],[2.1,-3.933],[0,-5.2],[-2.134,-3.866],[-3.767,-2.166],[-4.867,0]],"o":[[3.966,-3.133],[0,0],[-1,2.334],[-2.034,1.367],[-3.734,0],[-2.834,-2.5],[0,0],[0.066,-0.933],[0.033,-0.766],[0,-4.333],[-2,-3.6],[-3.6,-2.166],[-5,0],[-3.767,2.2],[-2.1,3.934],[0,5.134],[2.133,3.867],[3.766,2.166],[5.866,0]],"v":[[44.65,-3.5],[52.6,-15.2],[42,-15.2],[37.45,-9.65],[29.9,-7.6],[20.05,-11.35],[15.3,-22.3],[53.9,-22.3],[54.05,-24.85],[54.1,-27],[51.1,-38.9],[42.7,-47.55],[30,-50.8],[16.85,-47.5],[8.05,-38.3],[4.9,-24.6],[8.1,-11.1],[16.95,-2.05],[29.9,1.2]],"c":true},"ix":2},"nm":"e","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[-2.534,-2.266],[-0.2,-3.866],[0,0],[-2.734,2.1],[-3.534,0]],"o":[[2.533,2.267],[0,0],[0.6,-4.2],[2.733,-2.1],[3.8,0]],"v":[[39.4,-38.7],[43.5,-29.5],[15.5,-29.5],[20.5,-38.95],[29.9,-42.1]],"c":true},"ix":2},"nm":"e","mn":"ADBE Vector Shape - Group","hd":false}],"nm":"e","np":5,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}]},"fFamily":"DM Sans"},{"ch":"a","size":20,"style":"Medium","w":55.9,"data":{"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-2.667,1.866],[-1.4,2.734],[0,0],[0,0],[0,0],[3.8,3.634],[6.933,0],[3.233,-1.366],[2.1,-2.566],[0.333,-3.6],[0,0],[-2.167,1.434],[-2.934,0],[-2.1,-1.933],[0,-3.866],[0,0],[0,0],[3.733,-2.8],[0,-4.8],[-1.334,-2.3],[-2.734,-1.4],[-4.2,0]],"o":[[2.666,-1.866],[0,0],[0,0],[0,0],[0,-6.266],[-3.8,-3.633],[-3.867,0],[-3.234,1.367],[-2.1,2.567],[0,0],[0.466,-2.8],[2.166,-1.433],[3.333,0],[2.1,1.934],[0,0],[0,0],[-6.934,0],[-3.734,2.8],[0,2.734],[1.333,2.3],[2.733,1.4],[4.333,0]],"v":[[34,-1.6],[40.1,-8.5],[41,0],[50.2,0],[50.2,-30.5],[44.5,-45.35],[28.4,-50.8],[17.75,-48.75],[9.75,-42.85],[6.1,-33.6],[16.7,-33.6],[20.65,-39.95],[28.3,-42.1],[36.45,-39.2],[39.6,-30.5],[39.6,-29.6],[26.6,-29.6],[10.6,-25.4],[5,-14],[7,-6.45],[13.1,-0.9],[23.5,1.2]],"c":true},"ix":2},"nm":"a","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[1.7,1.3],[0,2.267],[-1.767,1.467],[-4.2,0],[0,0],[1.1,-2.266],[1.966,-1.366],[2.866,0]],"o":[[-1.7,-1.3],[0,-2.2],[1.766,-1.466],[0,0],[-0.267,2.734],[-1.1,2.267],[-1.967,1.367],[-2.934,0]],"v":[[18.65,-9.35],[16.1,-14.7],[18.75,-20.2],[27.7,-22.4],[39.5,-22.4],[37.45,-14.9],[32.85,-9.45],[25.6,-7.4]],"c":true},"ix":2},"nm":"a","mn":"ADBE Vector Shape - Group","hd":false}],"nm":"a","np":5,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}]},"fFamily":"DM Sans"},{"ch":"r","size":20,"style":"Medium","w":38,"data":{"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[-1.367,2.3],[-2.3,0.967],[-2.934,0],[0,0],[0,0],[3.1,-1.9],[1.733,-3.266],[0,0],[0,0],[0,0]],"o":[[0,0],[0,-4.066],[1.366,-2.3],[2.3,-0.966],[0,0],[0,0],[-4.4,0],[-3.1,1.9],[0,0],[0,0],[0,0],[0,0]],"v":[[17.3,0],[17.3,-23.8],[19.35,-33.35],[24.85,-38.25],[32.7,-39.7],[35.6,-39.7],[35.6,-50.8],[24.35,-47.95],[17.1,-40.2],[16.2,-49.6],[6.7,-49.6],[6.7,0]],"c":true},"ix":2},"nm":"r","mn":"ADBE Vector Shape - Group","hd":false}],"nm":"r","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}]},"fFamily":"DM Sans"},{"ch":"n","size":20,"style":"Medium","w":59,"data":{"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[-2.567,2.8],[-3.934,0],[0,-9.6],[0,0],[0,0],[0,0],[3.433,3.734],[6,0],[2.966,-1.766],[1.533,-3.066],[0,0],[0,0],[0,0]],"o":[[0,0],[0,-5.2],[2.566,-2.8],[7.866,0],[0,0],[0,0],[0,0],[0,-7.4],[-3.434,-3.733],[-3.867,0],[-2.967,1.767],[0,0],[0,0],[0,0],[0,0]],"v":[[17.3,0],[17.3,-25.6],[21.15,-37.6],[30.9,-41.8],[42.7,-27.4],[42.7,0],[53.2,0],[53.2,-28.5],[48.05,-45.2],[33.9,-50.8],[23.65,-48.15],[16.9,-40.9],[16.1,-49.6],[6.7,-49.6],[6.7,0]],"c":true},"ix":2},"nm":"n","mn":"ADBE Vector Shape - Group","hd":false}],"nm":"n","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}]},"fFamily":"DM Sans"},{"ch":" ","size":20,"style":"Medium","w":25.2,"data":{},"fFamily":"DM Sans"},{"ch":"M","size":20,"style":"Medium","w":86.5,"data":{"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[17.7,0],[17.7,-51.6],[39.2,-10],[47.4,-10],[68.8,-51.7],[68.8,0],[79.4,0],[79.4,-70],[66.9,-70],[43.3,-23.4],[19.6,-70],[7.1,-70],[7.1,0]],"c":true},"ix":2},"nm":"M","mn":"ADBE Vector Shape - Group","hd":false}],"nm":"M","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}]},"fFamily":"DM Sans"},{"ch":"o","size":20,"style":"Medium","w":59.9,"data":{"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-3.8,2.166],[-2.234,3.9],[0,5.2],[2.233,3.9],[3.766,2.167],[4.733,0],[3.8,-2.166],[2.233,-3.9],[0,-5.2],[-2.2,-3.9],[-3.767,-2.166],[-4.734,0]],"o":[[3.8,-2.166],[2.233,-3.9],[0,-5.2],[-2.234,-3.9],[-3.767,-2.166],[-4.667,0],[-3.8,2.167],[-2.234,3.9],[0,5.2],[2.2,3.9],[3.766,2.166],[4.733,0]],"v":[[42.7,-2.05],[51.75,-11.15],[55.1,-24.8],[51.75,-38.45],[42.75,-47.55],[30,-50.8],[17.3,-47.55],[8.25,-38.45],[4.9,-24.8],[8.2,-11.15],[17.15,-2.05],[29.9,1.2]],"c":true},"ix":2},"nm":"o","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[2.133,1.267],[1.333,2.5],[0,3.734],[-1.334,2.5],[-2.167,1.267],[-2.534,0],[-2.167,-1.266],[-1.3,-2.5],[0,-3.733],[1.333,-2.5],[2.166,-1.266],[2.533,0]],"o":[[-2.134,-1.266],[-1.334,-2.5],[0,-3.733],[1.333,-2.5],[2.166,-1.266],[2.533,0],[2.166,1.267],[1.3,2.5],[0,3.734],[-1.334,2.5],[-2.167,1.267],[-2.534,0]],"v":[[22.9,-9.8],[17.7,-15.45],[15.7,-24.8],[17.7,-34.15],[22.95,-39.8],[30,-41.7],[37.05,-39.8],[42.25,-34.15],[44.2,-24.8],[42.2,-15.45],[36.95,-9.8],[29.9,-7.9]],"c":true},"ix":2},"nm":"o","mn":"ADBE Vector Shape - Group","hd":false}],"nm":"o","np":5,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}]},"fFamily":"DM Sans"}]} \ No newline at end of file diff --git a/source/images/mam_flow_payment_link.png b/source/images/mam_flow_payment_link.png new file mode 100644 index 00000000000..6407edcf245 Binary files /dev/null and b/source/images/mam_flow_payment_link.png differ diff --git a/source/images/manual_topup.png b/source/images/manual_topup.png new file mode 100644 index 00000000000..30cbe1496d5 Binary files /dev/null and b/source/images/manual_topup.png differ diff --git a/source/images/menu-outline.svg b/source/images/menu-outline.svg new file mode 100644 index 00000000000..8337b091721 --- /dev/null +++ b/source/images/menu-outline.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/source/images/multi-layer-approval.png b/source/images/multi-layer-approval.png new file mode 100644 index 00000000000..6123b5b683c Binary files /dev/null and b/source/images/multi-layer-approval.png differ diff --git a/source/images/multi-level-approval-detail.png b/source/images/multi-level-approval-detail.png new file mode 100644 index 00000000000..5d060b829b7 Binary files /dev/null and b/source/images/multi-level-approval-detail.png differ diff --git a/source/images/one_time_link_list.png b/source/images/one_time_link_list.png new file mode 100644 index 00000000000..2e336c5b2fe Binary files /dev/null and b/source/images/one_time_link_list.png differ diff --git a/source/images/one_time_pc_mam_flow.png b/source/images/one_time_pc_mam_flow.png new file mode 100644 index 00000000000..38b69eb5051 Binary files /dev/null and b/source/images/one_time_pc_mam_flow.png differ diff --git a/source/images/oy-dashboard-tutorial.svg b/source/images/oy-dashboard-tutorial.svg new file mode 100644 index 00000000000..94f19beae5b --- /dev/null +++ b/source/images/oy-dashboard-tutorial.svg @@ -0,0 +1,3 @@ + + + diff --git a/source/images/oy-docs.png b/source/images/oy-docs.png new file mode 100644 index 00000000000..7d91ee6bfc0 Binary files /dev/null and b/source/images/oy-docs.png differ diff --git a/source/images/oy-docs.svg b/source/images/oy-docs.svg new file mode 100644 index 00000000000..083e8b17a15 --- /dev/null +++ b/source/images/oy-docs.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/source/images/oy_auth_configuration.png b/source/images/oy_auth_configuration.png new file mode 100644 index 00000000000..2c7db532d18 Binary files /dev/null and b/source/images/oy_auth_configuration.png differ diff --git a/source/images/oy_bisnis_app_account.jpg b/source/images/oy_bisnis_app_account.jpg new file mode 100644 index 00000000000..1fe5ee20b22 Binary files /dev/null and b/source/images/oy_bisnis_app_account.jpg differ diff --git a/source/images/oy_bisnis_app_buat_link_pembayaran.jpg b/source/images/oy_bisnis_app_buat_link_pembayaran.jpg new file mode 100644 index 00000000000..1a29750088a Binary files /dev/null and b/source/images/oy_bisnis_app_buat_link_pembayaran.jpg differ diff --git a/source/images/oy_bisnis_app_input_phone_number.jpg b/source/images/oy_bisnis_app_input_phone_number.jpg new file mode 100644 index 00000000000..d4b9f6e9198 Binary files /dev/null and b/source/images/oy_bisnis_app_input_phone_number.jpg differ diff --git a/source/images/oy_bisnis_app_konfigurasi_link_pembayaran.jpg b/source/images/oy_bisnis_app_konfigurasi_link_pembayaran.jpg new file mode 100644 index 00000000000..9faced47885 Binary files /dev/null and b/source/images/oy_bisnis_app_konfigurasi_link_pembayaran.jpg differ diff --git a/source/images/oy_bisnis_app_new_homepage.jpg b/source/images/oy_bisnis_app_new_homepage.jpg new file mode 100644 index 00000000000..fb32cebcaac Binary files /dev/null and b/source/images/oy_bisnis_app_new_homepage.jpg differ diff --git a/source/images/oy_bisnis_app_new_homepage_done_kyb.png b/source/images/oy_bisnis_app_new_homepage_done_kyb.png new file mode 100644 index 00000000000..962e08e573a Binary files /dev/null and b/source/images/oy_bisnis_app_new_homepage_done_kyb.png differ diff --git a/source/images/oy_bisnis_app_otp.jpg b/source/images/oy_bisnis_app_otp.jpg new file mode 100644 index 00000000000..fe04df2235f Binary files /dev/null and b/source/images/oy_bisnis_app_otp.jpg differ diff --git a/source/images/oy_bisnis_app_register.jpg b/source/images/oy_bisnis_app_register.jpg new file mode 100644 index 00000000000..40e1df53157 Binary files /dev/null and b/source/images/oy_bisnis_app_register.jpg differ diff --git a/source/images/oy_bisnis_app_select_transaction.png b/source/images/oy_bisnis_app_select_transaction.png new file mode 100644 index 00000000000..f6930b878f6 Binary files /dev/null and b/source/images/oy_bisnis_app_select_transaction.png differ diff --git a/source/images/oy_bisnis_app_submit_buat_pembayaran.jpg b/source/images/oy_bisnis_app_submit_buat_pembayaran.jpg new file mode 100644 index 00000000000..096cee3fe1a Binary files /dev/null and b/source/images/oy_bisnis_app_submit_buat_pembayaran.jpg differ diff --git a/source/images/oy_bisnis_create_disbursement.jpg b/source/images/oy_bisnis_create_disbursement.jpg new file mode 100644 index 00000000000..0afd771e2ae Binary files /dev/null and b/source/images/oy_bisnis_create_disbursement.jpg differ diff --git a/source/images/oy_bisnis_disbursement_campaign_approve.jpg b/source/images/oy_bisnis_disbursement_campaign_approve.jpg new file mode 100644 index 00000000000..f4f2949ab3c Binary files /dev/null and b/source/images/oy_bisnis_disbursement_campaign_approve.jpg differ diff --git a/source/images/oy_bisnis_disbursement_campaign_detail.jpg b/source/images/oy_bisnis_disbursement_campaign_detail.jpg new file mode 100644 index 00000000000..f7bf02f9352 Binary files /dev/null and b/source/images/oy_bisnis_disbursement_campaign_detail.jpg differ diff --git a/source/images/oy_bisnis_disbursement_campaign_name.jpg b/source/images/oy_bisnis_disbursement_campaign_name.jpg new file mode 100644 index 00000000000..de5cfaa57fa Binary files /dev/null and b/source/images/oy_bisnis_disbursement_campaign_name.jpg differ diff --git a/source/images/oy_bisnis_disbursement_detail_campaign.jpg b/source/images/oy_bisnis_disbursement_detail_campaign.jpg new file mode 100644 index 00000000000..edccb1a822e Binary files /dev/null and b/source/images/oy_bisnis_disbursement_detail_campaign.jpg differ diff --git a/source/images/oy_bisnis_disbursement_recipient_list.jpg b/source/images/oy_bisnis_disbursement_recipient_list.jpg new file mode 100644 index 00000000000..ed2a34d8636 Binary files /dev/null and b/source/images/oy_bisnis_disbursement_recipient_list.jpg differ diff --git a/source/images/payment_checkout_1.png b/source/images/payment_checkout_1.png new file mode 100644 index 00000000000..eeded65a181 Binary files /dev/null and b/source/images/payment_checkout_1.png differ diff --git a/source/images/payment_checkout_2.png b/source/images/payment_checkout_2.png new file mode 100644 index 00000000000..115230ab1b1 Binary files /dev/null and b/source/images/payment_checkout_2.png differ diff --git a/source/images/payment_checkout_3.png b/source/images/payment_checkout_3.png new file mode 100644 index 00000000000..92db453a5a3 Binary files /dev/null and b/source/images/payment_checkout_3.png differ diff --git a/source/images/payment_checkout_complete.png b/source/images/payment_checkout_complete.png new file mode 100644 index 00000000000..bd5a6bba680 Binary files /dev/null and b/source/images/payment_checkout_complete.png differ diff --git a/source/images/payment_checkout_info_customer.png b/source/images/payment_checkout_info_customer.png new file mode 100644 index 00000000000..ac50e69d235 Binary files /dev/null and b/source/images/payment_checkout_info_customer.png differ diff --git a/source/images/payment_checkout_input_amount.png b/source/images/payment_checkout_input_amount.png new file mode 100644 index 00000000000..c331a32237d Binary files /dev/null and b/source/images/payment_checkout_input_amount.png differ diff --git a/source/images/payment_checkout_list.png b/source/images/payment_checkout_list.png new file mode 100644 index 00000000000..84ef769d92a Binary files /dev/null and b/source/images/payment_checkout_list.png differ diff --git a/source/images/payment_checkout_payment_method.png b/source/images/payment_checkout_payment_method.png new file mode 100644 index 00000000000..48266892e95 Binary files /dev/null and b/source/images/payment_checkout_payment_method.png differ diff --git a/source/images/payment_link_resend_callback.png b/source/images/payment_link_resend_callback.png new file mode 100644 index 00000000000..9644cf0bc83 Binary files /dev/null and b/source/images/payment_link_resend_callback.png differ diff --git a/source/images/register_1.png b/source/images/register_1.png new file mode 100644 index 00000000000..e512db78936 Binary files /dev/null and b/source/images/register_1.png differ diff --git a/source/images/register_2.png b/source/images/register_2.png new file mode 100644 index 00000000000..f16c7379175 Binary files /dev/null and b/source/images/register_2.png differ diff --git a/source/images/reimbursement/Approver_List_Detail.png b/source/images/reimbursement/Approver_List_Detail.png new file mode 100644 index 00000000000..1503e87bd47 Binary files /dev/null and b/source/images/reimbursement/Approver_List_Detail.png differ diff --git a/source/images/reimbursement/Approver_Portal_List.png b/source/images/reimbursement/Approver_Portal_List.png new file mode 100644 index 00000000000..12862a88cce Binary files /dev/null and b/source/images/reimbursement/Approver_Portal_List.png differ diff --git a/source/images/reimbursement/Approver_Registration.png b/source/images/reimbursement/Approver_Registration.png new file mode 100644 index 00000000000..b0ddd28b2af Binary files /dev/null and b/source/images/reimbursement/Approver_Registration.png differ diff --git a/source/images/reimbursement/Employee_Tracker1.png b/source/images/reimbursement/Employee_Tracker1.png new file mode 100644 index 00000000000..041435b9011 Binary files /dev/null and b/source/images/reimbursement/Employee_Tracker1.png differ diff --git a/source/images/reimbursement/Employee_Tracker2.png b/source/images/reimbursement/Employee_Tracker2.png new file mode 100644 index 00000000000..8d936d47f13 Binary files /dev/null and b/source/images/reimbursement/Employee_Tracker2.png differ diff --git a/source/images/reimbursement/Reimbursement_Detail1.png b/source/images/reimbursement/Reimbursement_Detail1.png new file mode 100644 index 00000000000..518e40e7df5 Binary files /dev/null and b/source/images/reimbursement/Reimbursement_Detail1.png differ diff --git a/source/images/reimbursement/Reimbursement_Detail2.png b/source/images/reimbursement/Reimbursement_Detail2.png new file mode 100644 index 00000000000..dd2677011cb Binary files /dev/null and b/source/images/reimbursement/Reimbursement_Detail2.png differ diff --git a/source/images/reimbursement/Reimbursement_Link.png b/source/images/reimbursement/Reimbursement_Link.png new file mode 100644 index 00000000000..54107ec774c Binary files /dev/null and b/source/images/reimbursement/Reimbursement_Link.png differ diff --git a/source/images/reimbursement/Reimbursement_List.png b/source/images/reimbursement/Reimbursement_List.png new file mode 100644 index 00000000000..6bf00bf9624 Binary files /dev/null and b/source/images/reimbursement/Reimbursement_List.png differ diff --git a/source/images/reimbursement/Reimbursement_Step1.png b/source/images/reimbursement/Reimbursement_Step1.png new file mode 100644 index 00000000000..eaffc35af4b Binary files /dev/null and b/source/images/reimbursement/Reimbursement_Step1.png differ diff --git a/source/images/reimbursement/Reimbursement_Step2.png b/source/images/reimbursement/Reimbursement_Step2.png new file mode 100644 index 00000000000..12643226898 Binary files /dev/null and b/source/images/reimbursement/Reimbursement_Step2.png differ diff --git a/source/images/reimbursement/reimbursement_form_1.png b/source/images/reimbursement/reimbursement_form_1.png new file mode 100644 index 00000000000..4b7b316ea71 Binary files /dev/null and b/source/images/reimbursement/reimbursement_form_1.png differ diff --git a/source/images/reimbursement/reimbursement_form_2_filled.png b/source/images/reimbursement/reimbursement_form_2_filled.png new file mode 100644 index 00000000000..63169d3d2ed Binary files /dev/null and b/source/images/reimbursement/reimbursement_form_2_filled.png differ diff --git a/source/images/retry_callback_developer_option.png b/source/images/retry_callback_developer_option.png new file mode 100644 index 00000000000..48c576baf51 Binary files /dev/null and b/source/images/retry_callback_developer_option.png differ diff --git a/source/images/reusable_link_child.png b/source/images/reusable_link_child.png new file mode 100644 index 00000000000..1407ee19bd9 Binary files /dev/null and b/source/images/reusable_link_child.png differ diff --git a/source/images/reusable_link_parent.png b/source/images/reusable_link_parent.png new file mode 100644 index 00000000000..01663ef48e8 Binary files /dev/null and b/source/images/reusable_link_parent.png differ diff --git a/source/images/reusable_link_parent_details.png b/source/images/reusable_link_parent_details.png new file mode 100644 index 00000000000..02c20cb1107 Binary files /dev/null and b/source/images/reusable_link_parent_details.png differ diff --git a/source/images/reusable_pc_mam_flow.png b/source/images/reusable_pc_mam_flow.png new file mode 100644 index 00000000000..d07a6667bf1 Binary files /dev/null and b/source/images/reusable_pc_mam_flow.png differ diff --git a/source/images/reusable_resend_callback.png b/source/images/reusable_resend_callback.png new file mode 100644 index 00000000000..bc194c904ab Binary files /dev/null and b/source/images/reusable_resend_callback.png differ diff --git a/source/images/search.svg b/source/images/search.svg new file mode 100644 index 00000000000..09aa72b9242 --- /dev/null +++ b/source/images/search.svg @@ -0,0 +1,3 @@ + + + diff --git a/source/images/sending-payments.svg b/source/images/sending-payments.svg new file mode 100644 index 00000000000..10f2c1443e2 --- /dev/null +++ b/source/images/sending-payments.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/source/images/settlement_report.png b/source/images/settlement_report.png new file mode 100644 index 00000000000..40973706e6d Binary files /dev/null and b/source/images/settlement_report.png differ diff --git a/source/images/setup_bank_account.png b/source/images/setup_bank_account.png new file mode 100644 index 00000000000..33e9dbfead9 Binary files /dev/null and b/source/images/setup_bank_account.png differ diff --git a/source/images/ssd-soal-sering-ditanya.svg b/source/images/ssd-soal-sering-ditanya.svg new file mode 100644 index 00000000000..427c7688e83 --- /dev/null +++ b/source/images/ssd-soal-sering-ditanya.svg @@ -0,0 +1,3 @@ + + + diff --git a/source/images/success_failed_confirmation.png b/source/images/success_failed_confirmation.png new file mode 100644 index 00000000000..278d63609b3 Binary files /dev/null and b/source/images/success_failed_confirmation.png differ diff --git a/source/images/terima-uang.svg b/source/images/terima-uang.svg new file mode 100644 index 00000000000..4f1e1b47454 --- /dev/null +++ b/source/images/terima-uang.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/source/images/topup_confirmation_dashboard.png b/source/images/topup_confirmation_dashboard.png new file mode 100644 index 00000000000..ef68de46a58 Binary files /dev/null and b/source/images/topup_confirmation_dashboard.png differ diff --git a/source/images/topup_confirmation_email.png b/source/images/topup_confirmation_email.png new file mode 100644 index 00000000000..aac8ad1f179 Binary files /dev/null and b/source/images/topup_confirmation_email.png differ diff --git a/source/images/tutorial-dashboard-oy.svg b/source/images/tutorial-dashboard-oy.svg new file mode 100644 index 00000000000..94f19beae5b --- /dev/null +++ b/source/images/tutorial-dashboard-oy.svg @@ -0,0 +1,3 @@ + + + diff --git a/source/images/ui_customization_color_logo.png b/source/images/ui_customization_color_logo.png new file mode 100644 index 00000000000..66fb64d4a52 Binary files /dev/null and b/source/images/ui_customization_color_logo.png differ diff --git a/source/images/ui_customization_result_1.png b/source/images/ui_customization_result_1.png new file mode 100644 index 00000000000..e0f23b4e293 Binary files /dev/null and b/source/images/ui_customization_result_1.png differ diff --git a/source/images/ui_customization_result_2.png b/source/images/ui_customization_result_2.png new file mode 100644 index 00000000000..5a5b2de65d3 Binary files /dev/null and b/source/images/ui_customization_result_2.png differ diff --git a/source/images/ui_customization_result_3.png b/source/images/ui_customization_result_3.png new file mode 100644 index 00000000000..7abcdcef5d1 Binary files /dev/null and b/source/images/ui_customization_result_3.png differ diff --git a/source/images/ui_customization_settings_icon.png b/source/images/ui_customization_settings_icon.png new file mode 100644 index 00000000000..b897cfcb2e9 Binary files /dev/null and b/source/images/ui_customization_settings_icon.png differ diff --git a/source/images/ui_customization_settings_page.png b/source/images/ui_customization_settings_page.png new file mode 100644 index 00000000000..c85f35465aa Binary files /dev/null and b/source/images/ui_customization_settings_page.png differ diff --git a/source/images/upgrade_1.png b/source/images/upgrade_1.png new file mode 100644 index 00000000000..ea582496b8c Binary files /dev/null and b/source/images/upgrade_1.png differ diff --git a/source/images/upgrade_2.png b/source/images/upgrade_2.png new file mode 100644 index 00000000000..c6676617c35 Binary files /dev/null and b/source/images/upgrade_2.png differ diff --git a/source/images/upgrade_3.png b/source/images/upgrade_3.png new file mode 100644 index 00000000000..fd6b6a4606e Binary files /dev/null and b/source/images/upgrade_3.png differ diff --git a/source/images/upgrade_account_1.png b/source/images/upgrade_account_1.png new file mode 100644 index 00000000000..caf1c025feb Binary files /dev/null and b/source/images/upgrade_account_1.png differ diff --git a/source/images/upgrade_account_2.png b/source/images/upgrade_account_2.png new file mode 100644 index 00000000000..18d49d7fd94 Binary files /dev/null and b/source/images/upgrade_account_2.png differ diff --git a/source/images/upgrade_account_3.png b/source/images/upgrade_account_3.png new file mode 100644 index 00000000000..0aa5f95c9cc Binary files /dev/null and b/source/images/upgrade_account_3.png differ diff --git a/source/images/upgrade_account_4.png b/source/images/upgrade_account_4.png new file mode 100644 index 00000000000..489df1e4583 Binary files /dev/null and b/source/images/upgrade_account_4.png differ diff --git a/source/images/upgrade_account_5.png b/source/images/upgrade_account_5.png new file mode 100644 index 00000000000..3cddd456e99 Binary files /dev/null and b/source/images/upgrade_account_5.png differ diff --git a/source/images/upgrade_account_6.png b/source/images/upgrade_account_6.png new file mode 100644 index 00000000000..563d1f9c996 Binary files /dev/null and b/source/images/upgrade_account_6.png differ diff --git a/source/images/upgrade_account_7.png b/source/images/upgrade_account_7.png new file mode 100644 index 00000000000..3c1cd763892 Binary files /dev/null and b/source/images/upgrade_account_7.png differ diff --git a/source/images/user_management_1.png b/source/images/user_management_1.png new file mode 100644 index 00000000000..c3035606ef0 Binary files /dev/null and b/source/images/user_management_1.png differ diff --git a/source/images/user_management_2.png b/source/images/user_management_2.png new file mode 100644 index 00000000000..56957d355ab Binary files /dev/null and b/source/images/user_management_2.png differ diff --git a/source/images/va_created_va.png b/source/images/va_created_va.png new file mode 100644 index 00000000000..9a983ee84ac Binary files /dev/null and b/source/images/va_created_va.png differ diff --git a/source/images/va_diagram_1.png b/source/images/va_diagram_1.png new file mode 100644 index 00000000000..88cccad18da Binary files /dev/null and b/source/images/va_diagram_1.png differ diff --git a/source/images/va_diagram_2.png b/source/images/va_diagram_2.png new file mode 100644 index 00000000000..08cbf870972 Binary files /dev/null and b/source/images/va_diagram_2.png differ diff --git a/source/images/va_incoming.png b/source/images/va_incoming.png new file mode 100644 index 00000000000..9d58798bf3a Binary files /dev/null and b/source/images/va_incoming.png differ diff --git a/source/images/va_topup.png b/source/images/va_topup.png new file mode 100644 index 00000000000..fb3ae7ee310 Binary files /dev/null and b/source/images/va_topup.png differ diff --git a/source/images/va_use_case_new.png b/source/images/va_use_case_new.png new file mode 100644 index 00000000000..f3c99b57f12 Binary files /dev/null and b/source/images/va_use_case_new.png differ diff --git a/source/images/va_waiting_payment.png b/source/images/va_waiting_payment.png new file mode 100644 index 00000000000..32d5c8a3c83 Binary files /dev/null and b/source/images/va_waiting_payment.png differ diff --git a/source/images/vatopupnobni.png b/source/images/vatopupnobni.png new file mode 100644 index 00000000000..2635596e6f9 Binary files /dev/null and b/source/images/vatopupnobni.png differ diff --git a/source/images/vendorManagement/creation.png b/source/images/vendorManagement/creation.png new file mode 100644 index 00000000000..b0e4698b213 Binary files /dev/null and b/source/images/vendorManagement/creation.png differ diff --git a/source/images/vendorManagement/detail.png b/source/images/vendorManagement/detail.png new file mode 100644 index 00000000000..e1b3c6a09c6 Binary files /dev/null and b/source/images/vendorManagement/detail.png differ diff --git a/source/images/vendorManagement/list.png b/source/images/vendorManagement/list.png new file mode 100644 index 00000000000..07c33751089 Binary files /dev/null and b/source/images/vendorManagement/list.png differ diff --git a/source/images/virtualCard/add_approver_form.png b/source/images/virtualCard/add_approver_form.png new file mode 100644 index 00000000000..d7814bec33e Binary files /dev/null and b/source/images/virtualCard/add_approver_form.png differ diff --git a/source/images/virtualCard/add_new_approver.png b/source/images/virtualCard/add_new_approver.png new file mode 100644 index 00000000000..309750e92e0 Binary files /dev/null and b/source/images/virtualCard/add_new_approver.png differ diff --git a/source/images/virtualCard/corporate_card_dashboard.png b/source/images/virtualCard/corporate_card_dashboard.png new file mode 100644 index 00000000000..61213edffe4 Binary files /dev/null and b/source/images/virtualCard/corporate_card_dashboard.png differ diff --git a/source/images/virtualCard/vcc_approver_list.png b/source/images/virtualCard/vcc_approver_list.png new file mode 100644 index 00000000000..bb0e40812d7 Binary files /dev/null and b/source/images/virtualCard/vcc_approver_list.png differ diff --git a/source/images/virtualCard/vcc_card_list.jpg b/source/images/virtualCard/vcc_card_list.jpg new file mode 100644 index 00000000000..8ade2cd78e1 Binary files /dev/null and b/source/images/virtualCard/vcc_card_list.jpg differ diff --git a/source/images/virtualCard/vcc_category_list.png b/source/images/virtualCard/vcc_category_list.png new file mode 100644 index 00000000000..dfb18ab5393 Binary files /dev/null and b/source/images/virtualCard/vcc_category_list.png differ diff --git a/source/images/virtualCard/vcc_department_list.png b/source/images/virtualCard/vcc_department_list.png new file mode 100644 index 00000000000..4008358d620 Binary files /dev/null and b/source/images/virtualCard/vcc_department_list.png differ diff --git a/source/images/virtualCard/vcc_edit_category.png b/source/images/virtualCard/vcc_edit_category.png new file mode 100644 index 00000000000..8e51ae12884 Binary files /dev/null and b/source/images/virtualCard/vcc_edit_category.png differ diff --git a/source/images/virtualCard/vcc_edit_department.png b/source/images/virtualCard/vcc_edit_department.png new file mode 100644 index 00000000000..a6f97deff17 Binary files /dev/null and b/source/images/virtualCard/vcc_edit_department.png differ diff --git a/source/images/virtualCard/vcc_email_success_creation.png b/source/images/virtualCard/vcc_email_success_creation.png new file mode 100644 index 00000000000..0b02db86c4d Binary files /dev/null and b/source/images/virtualCard/vcc_email_success_creation.png differ diff --git a/source/images/virtualCard/vcc_form_1.png b/source/images/virtualCard/vcc_form_1.png new file mode 100644 index 00000000000..7471b62dab5 Binary files /dev/null and b/source/images/virtualCard/vcc_form_1.png differ diff --git a/source/images/virtualCard/vcc_form_2.png b/source/images/virtualCard/vcc_form_2.png new file mode 100644 index 00000000000..0ef443ebd16 Binary files /dev/null and b/source/images/virtualCard/vcc_form_2.png differ diff --git a/source/images/virtualCard/vcc_transaction_details.png b/source/images/virtualCard/vcc_transaction_details.png new file mode 100644 index 00000000000..67bab2b3bd7 Binary files /dev/null and b/source/images/virtualCard/vcc_transaction_details.png differ diff --git a/source/images/virtualCard/vcc_trx_email.png b/source/images/virtualCard/vcc_trx_email.png new file mode 100644 index 00000000000..1e4fa3ae96a Binary files /dev/null and b/source/images/virtualCard/vcc_trx_email.png differ diff --git a/source/images/virtualCard/virtual_card_info.png b/source/images/virtualCard/virtual_card_info.png new file mode 100644 index 00000000000..0bccf82db5a Binary files /dev/null and b/source/images/virtualCard/virtual_card_info.png differ diff --git a/source/images/virtualCard/virtual_card_type.png b/source/images/virtualCard/virtual_card_type.png new file mode 100644 index 00000000000..cd68d31a512 Binary files /dev/null and b/source/images/virtualCard/virtual_card_type.png differ diff --git a/source/images/withdrawal_topup.png b/source/images/withdrawal_topup.png new file mode 100644 index 00000000000..32a1c673f01 Binary files /dev/null and b/source/images/withdrawal_topup.png differ diff --git a/source/images/xero/after_connected.png b/source/images/xero/after_connected.png new file mode 100644 index 00000000000..7c0db0ead8f Binary files /dev/null and b/source/images/xero/after_connected.png differ diff --git a/source/images/xero/bulk_disburse_with_xero.png b/source/images/xero/bulk_disburse_with_xero.png new file mode 100644 index 00000000000..169501d0137 Binary files /dev/null and b/source/images/xero/bulk_disburse_with_xero.png differ diff --git a/source/images/xero/coa_mapping.png b/source/images/xero/coa_mapping.png new file mode 100644 index 00000000000..6c69393c35b Binary files /dev/null and b/source/images/xero/coa_mapping.png differ diff --git a/source/images/xero/coa_mapping_modal.png b/source/images/xero/coa_mapping_modal.png new file mode 100644 index 00000000000..61e662c2d2a Binary files /dev/null and b/source/images/xero/coa_mapping_modal.png differ diff --git a/source/images/xero/coa_mapping_modal_2.png b/source/images/xero/coa_mapping_modal_2.png new file mode 100644 index 00000000000..42ee55fc883 Binary files /dev/null and b/source/images/xero/coa_mapping_modal_2.png differ diff --git a/source/images/xero/connect_success.png b/source/images/xero/connect_success.png new file mode 100644 index 00000000000..66b831931b2 Binary files /dev/null and b/source/images/xero/connect_success.png differ diff --git a/source/images/xero/connect_success_notification.png b/source/images/xero/connect_success_notification.png new file mode 100644 index 00000000000..6b26dd1eb1b Binary files /dev/null and b/source/images/xero/connect_success_notification.png differ diff --git a/source/images/xero/consent_page.png b/source/images/xero/consent_page.png new file mode 100644 index 00000000000..2e4f9a4deab Binary files /dev/null and b/source/images/xero/consent_page.png differ diff --git a/source/images/xero/jurnal_login.png b/source/images/xero/jurnal_login.png new file mode 100644 index 00000000000..a4ea38a0eae Binary files /dev/null and b/source/images/xero/jurnal_login.png differ diff --git a/source/images/xero/login_xero.png b/source/images/xero/login_xero.png new file mode 100644 index 00000000000..d88871c96f4 Binary files /dev/null and b/source/images/xero/login_xero.png differ diff --git a/source/images/xero/menu.png b/source/images/xero/menu.png new file mode 100644 index 00000000000..bd87249ca37 Binary files /dev/null and b/source/images/xero/menu.png differ diff --git a/source/images/xero/new_account.png b/source/images/xero/new_account.png new file mode 100644 index 00000000000..e5c3b231a91 Binary files /dev/null and b/source/images/xero/new_account.png differ diff --git a/source/images/xero/product_list.png b/source/images/xero/product_list.png new file mode 100644 index 00000000000..8aada570a3f Binary files /dev/null and b/source/images/xero/product_list.png differ diff --git a/source/images/xero/recorded_transaction.png b/source/images/xero/recorded_transaction.png new file mode 100644 index 00000000000..9a6460caa81 Binary files /dev/null and b/source/images/xero/recorded_transaction.png differ diff --git a/source/images/xero/recorded_transaction_2.png b/source/images/xero/recorded_transaction_2.png new file mode 100644 index 00000000000..c330aa7c38e Binary files /dev/null and b/source/images/xero/recorded_transaction_2.png differ diff --git a/source/includes/_errors.md b/source/includes/_errors.md deleted file mode 100644 index 56cffb34d22..00000000000 --- a/source/includes/_errors.md +++ /dev/null @@ -1,20 +0,0 @@ -# Errors - - - -The Kittn API uses the following error codes: - - -Error Code | Meaning ----------- | ------- -400 | Bad Request -- Your request sucks -401 | Unauthorized -- Your API key is wrong -403 | Forbidden -- The kitten requested is hidden for administrators only -404 | Not Found -- The specified kitten could not be found -405 | Method Not Allowed -- You tried to access a kitten with an invalid method -406 | Not Acceptable -- You requested a format that isn't json -410 | Gone -- The kitten requested has been removed from our servers -418 | I'm a teapot -429 | Too Many Requests -- You're requesting too many kittens! Slow down! -500 | Internal Server Error -- We had a problem with our server. Try again later. -503 | Service Unavailable -- We're temporarially offline for maintanance. Please try again later. diff --git a/source/includes/en/_accepting-payments.md b/source/includes/en/_accepting-payments.md new file mode 100644 index 00000000000..651cdccf75d --- /dev/null +++ b/source/includes/en/_accepting-payments.md @@ -0,0 +1,2329 @@ +# Accepting Payments +## Payment Methods + +### Bank Transfer - Virtual Account +#### Introduction +To receive payments digitally from your customers, you can create a virtual account (VA) number for your transactions. Your customers can directly transfer the payment to the generated VA number and you will receive a notification (i.e. callback) from OY! once the transaction is considered complete. + +Currently, we offer VA service for 8 banks: + +1. Bank Central Asia (BCA) +1. Bank Rakyat Indonesia (BRI) +1. Bank Mandiri +1. Bank Negara Indonesia (BNI) +1. Bank CIMB & CIMB Syariah +1. Bank SMBC +1. Bank Syariah Indonesia (BSI) +1. Bank Permata & Permata Syariah + +#### Main Features Availability +|VA Features|Payment Link|VA Aggregator|E-Wallet Aggregator|Payment Routing| +| :- | :-: | :-: | :-: | :-: | +|Open Amount|❌|✅|❌|❌| +|Closed Amount |✅|✅|❌|✅| +|Multiple Use VA Number |❌|✅|❌|✅| +|Single Use VA Number|✅|✅|❌|✅| +|Static (Lifetime)|❌|✅|❌|✅| +|Dynamic |✅(set to 24 hours after VA number is generated)|✅|❌|✅| +|Customizable Number|❌|✅|❌|❌| + + +Regardless of the parameters of the Payment Link (e.g. open amount payment link, lifetime payment link, etc), your customers need to input the specific amount in the link to be able to choose a payment method successfully and proceed to payment. This is why if your customers choose to pay via VA - Payment Link, then the VA number generated can only accept the specified amount (i.e. closed amount) and can only be used for that particular transaction only (i.e. single use VA number). The expiry time for the VA generated via Payment Link is 24 hours after payment method confirmation from your customers. + +If you want to add a personal touch to your customer’s VA payment journey, you may do so by creating a customized VA number. With this feature, you can custom the suffix of the VA number according to your customer’s billing number or phone number. For example, if you customer’s phone number is 08123456789, then when you create a customized VA number, the VA number result will be 23088123456789. + +To create customized VA numbers, you need to hit a different URL endpoint than the usual VA creation endpoint, the [API Create Customized VA](https://api-docs.oyindonesia.com/#create-customized-va-va-aggregator). The API endpoints to [update](https://api-docs.oyindonesia.com/#update-customized-va-va-aggregator) and [deactivate](https://api-docs.oyindonesia.com/#deactivate-customized-va-va-aggregator) the customized VA number are also different from the non-customized VA number. In general, the customized VA numbers can receive multiple payments and have lifetime validity. Currently, this feature is only supported for BRI and CIMB. To activate this feature, you may contact your business representative for assistance. + +#### Transaction Amount Details + +Minimum amount per transaction + + + +|Product Type|Min. VA Transaction Amount| +| :-: | :-: | +|Payment Link (non CIMB, Mandiri, Permata, BNI, BRI) |Rp 10,000 | +|Payment Link (VA CIMB, Mandiri, Permata, BNI, BRI) |Rp 15,000 | +|Payment Routing|Rp 10,000 | +|VA Aggregator (Closed Amount)|Rp 10,000 | +|VA Aggregator (Open Amount)|No minimum amount | + +Maximum amount per transaction + + +|Banks|Max. Amount per transaction | +| :-: | :-: | +|Bank Central Asia (BCA) |Rp 50,000,000| +|Bank Negara Indonesia (BNI) |Rp 50,000,000| +|Bank Rakyat Indonesia (BRI) |Rp 500,000,000| +|Bank Mandiri |Rp 500,000,000| +|Bank CIMB |Rp 500,000,000| +|Bank SMBC |Rp 100,000,000| +|Bank Syariah Indonesia (BSI)|Rp 50,000,000| +|Bank Permata|Rp 500,000,000| + +#### Product Availability + +|Payment Link|VA Aggregator|E-Wallet Aggregator|Payment Routing| +| :-: | :-: | :-: | :-: | +|✅|✅|❌|✅| + + +#### Payment Flow +![Bank Transfer Virtual Account Flow](images/acceptingPayments/payment-methods/bank-transfer-virtual-account/payment-flow.webp) +#### Activation +1. Banks non BCA + - Generally, you do not need extra onboarding steps to activate VAs for each bank (non-BCA). Once you are allowed to use Receive Money products, you should be able to use the VAs without additional documents required. Kindly let your business representative know about the banks that you will need +1. BCA + - In addition to your onboarding documents, you also need to submit additional documents to be able to use VA for BCA (including, but not limited to, Taxpayer Registration Number (NPWP), and Nationality ID) + - Onboarding process to the bank will take around 14 to 30 working days, depending on the document completion and assessment from the bank + +#### Available Payment Channels for VA + + +Your end-users may use the below payment channels to pay for their bills via VA + + +| Bank (Virtual Account) | SKN | RTGS | ATMs | Intrabank Mobile Banking & Internet Banking | Interbank Internet Banking | Interbank Mobile Banking | +| ---------------------- | --- |---- |---- | ------------------------------------------- | --------------------------| ---------------------------- | +| Bank Mandiri | Yes | Yes | Yes | Yes | Yes | Yes | +| BRI | Yes | Yes | Yes | Yes | No | Yes | +| BNI | Yes | Yes | Yes | Yes | No | Yes | +| Permata | Yes | Yes | Yes | Yes | No | Yes +| CIMB Niaga / CIMB Niaga Syariah | Yes | Yes | Yes | Yes (Mobile Banking), No (Internet Banking)| No | Yes | +| BCA | No | No | Yes | Yes | No | No | +| SMBC | Yes | No | Yes | Yes (Mobile Banking), No (Internet Banking) | No | Yes | +| BSI | No | No | Yes | Yes | Yes | Yes | + + + +#### Simulating Callback +1. To simulate a successful payment, ensure that you are in our staging environment. Click “Try in Demo” button that will redirect you to our staging environment +1. Scroll down to “Settings” tab → Callback Bank Transfer +1. Choose the “Virtual Account” as the Transaction Type +1. Select the Bank Name of the VA number that you have previously created +1. Enter the VA number and amount. For Closed VA, you need to enter the exact amount of the VA as created +1. Enter the payment date and time. Ensure that payment date and time are greater than created but less than expiration time +1. You may use this feature for all your VA transactions across all of OY! Receive Money products (VA Aggregator, Payment Link, and Payment Routing) + +![Bank Transfer - Virtual Account Simulate Payment](images/acceptingPayments/payment-methods/bank-transfer-virtual-account/simulating-callback.webp) + + +### Bank Transfer - Unique Code +#### Intro +Unique Code is a type of bank transfer payment that adds/subtracts a unique amount (between Rp 1-999) to your billed amount. The unique amount acts as an identifier to complete transactions. Unlike virtual accounts where each customer gets a different account number, unique code always uses the same account number for all transactions. The destination account is under OY! Indonesia’s name (PT. Dompet Harapan Bangsa) and you can not modify the destination account with your account. Unique code also has operational hours where you can only create unique code transactions between 3 AM - 8.30 PM GMT+7. + +There are two approaches that you can use on unique code transactions: addition or subtraction approach. + +1. Addition Approach + - By using the addition approach, the unique amount is added to your billed amount, meaning your customer will pay Rp 1-999 more than the billed amount. The additional amount will not be settled to your balance. +1. Subtration Approach + - By using the subtraction approach, the billed amount is subtracted by the unique amount. In this case, your customer will pay Rp 1-999 less than the billed amount. However, worry not, as the amount settled to your balance is not deducted by the unique amount. The default approach is addition, but you can request to change the approach via your business representative. + +As an example, if you create a unique code transaction with an amount of Rp 100.000, then OY! will generate a unique amount for that transaction. Let’s assume the unique amount generated for that transaction is Rp 100. If you use the addition approach, then your customer pays a total of Rp 100.100. However, if you use the subtraction approach, your customer pays a total of Rp 99.900. Using either approach, the amount settled to your balance is Rp 100.000. + +#### Unique Code Payment Details + +|Banks|Bank Code|Open Amount |Closed Amount |Max. Expiration Time|Operational Hours| +| :-: | :-: | :-: | :-: | :-: | :-: | +|Bank Central Asia (BCA) |014|No|Yes|3 hours|03\.00 - 20.30 GMT + 7| + +#### Feature Availability + +|Banks|Refund Feature| +| :- | :- | +|BCA|No| + + +#### Product Availability + +|Bank |Payment Link|VA Aggregator|E-Wallet Aggregator|Payment Routing| +| :- | :-: | :-: | :-: | :-: | +|BCA |✅|❌|❌|✅| + +#### Transaction Amount Details + +|Approach Type|Min. Transaction Amount|Max. Transaction Amount| +| :-: | :-: | :-: | +|Subtraction|Rp 11,000 |Rp 500,000,000| +|Addition|Rp 10,000 |Rp 499,999,000| + +#### Payment Flow +![Bank Transfer - Unique Code Flow](images/acceptingPayments/payment-methods/bank-transfer-unique-code/payment-flow.webp) +#### Activation +You can only use one type of bank transfer (virtual account / unique code) per bank. By default, all banks use virtual accounts. In order to accept payments using unique code, you need to submit a request to OY! via your business representative or our business support. +#### Payment + +[Click here to see the payment journey](https://drive.google.com/file/d/1D8cJEPFmVEN8-QVppiSm9RPa2vpb-b2H/view?usp=drive_link) + +#### Simulate Payments +To get more understanding of unique code transactions behavior, you can simulate unique code transactions that are created in the Demo environment. Here are the steps to simulate unique code payments via OY! Dashboard: + +1. Open OY! Dashboard and navigate to the Demo environment +1. Open the “Settings” menu and click “Callback Bank Transfer”. +1. Insert the payment detail of the transaction you want to simulate: + - Transaction Type: Choose “Unique Code” + - Bank: Choose the destination bank. + - Account Number: Insert OY! Indonesia’s bank account number that you receive during creation + - Amount: Insert the billed amount and the unique amount that you receive during creation + - Payment Date and Time: Choose the date & time that you want the payment to occur +1. Once you input all the fields, you can simulate the payment by clicking “Send Callback”. If the payment is successful, a success notification will be shown inside the dashboard. OY! will also send the callback to your specified callback URL. If for some reason you did not receive any callback, please contact the customer service to help you solve the problem. + +![Bank Transfer - Unique Code Simulate Payment](images/acceptingPayments/payment-methods/bank-transfer-unique-code/simulating-callback.webp) + +![Bank Transfer - Unique Code Amount Detail](images/acceptingPayments/payment-methods/bank-transfer-unique-code/payment-link-view.webp) + + +### QR Code (QRIS) +#### Intro +Quick Response Code Indonesian Standard (QRIS) is a standardized QR payments in Indonesia that are developed by Bank Indonesia. Payments are performed by the customers scanning the QR on their m-banking/e-wallet application. QR payments are highly suitable for low-value transactions since they offer an affordable price (0.7% per transaction). + +#### Feature Availability + +|QRIS Provider|Refund Feature| +| :- | :- | +|QRIS|No| + +#### Product Availability + +|QRIS Provider|Payment Link|VA Aggregator|API E-Wallet Aggregator|Payment Routing| +| :- | :- | :- | :- | :- | +|QRIS|Yes|No|No|Yes| + +#### Transaction Amount Details + +The maximum amount per transaction for QRIS is Rp 10,000,000. The minimum amount per transaction is Rp 10,000, both in Payment Link and Payment Routing. Should you have any request to receive payments below Rp 10,000, please contact your Business Representative +#### Payment Flow +![QRIS Flow](images/acceptingPayments/payment-methods/qr-code-qris/payment-flow.webp) +#### Activation +In order to accept payments using QRIS, you need to register your merchant to the QRIS providers first. You can do the registration via OY! Dashboard by opening the Payment Method page and clicking the Payment Method tab. OY! offers real time registration, so you can directly accept payments once you finish submitting your application. + +Here are the requirements that must be fulfilled in order to submit a registration: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+

+

Requirement

+
QRIS
Business Ownership
Owner/Shareholder ID CardID Card Number
Full Name
Date of Birth
Occupation
Gender
Province
City
District
Village
Postal Code
Full Address
Owner/Shareholder Email Address
Owner/Shareholder Phone Number
NPWPNPWP Number
Document (in .jpg or .png)
Person in Charge (PIC) - DirectorFull Name
+

Position

+

+

+
Person in Charge (PIC) - Non DirectorFull Name
+

Position

+

+

+
Email Address
Phone Number
Payment Flow
Business Logo (in Google Drive URL format)
Website URL
Business Permit Number (According to the legality document)
Date of Business Establishment (According to the legality document)
Place of Business Establishment (According to the legality document)
Projected turnover per month using QRIS
Projected number of transactions per month using QRIS
+ + +#### Payments +[Click here to see the payment journey](https://drive.google.com/file/d/1nwoMKH8iKaq8S89an0_bPlNQ11xMyo7T/view?usp=sharing) + +#### Simulate Payments +Simulating QRIS payments for demo transactions is currently not available. + +### E-Wallet +#### Intro +E-wallet is a type of electronic payment that allows you to pay for goods and services without requiring bank accounts or cash. E-wallet plays a significant role in the rapid growth of e-commerce since it enables users to make payments easily without having to interact with banks or other third parties. OY! currently supports payment from several top e-wallets in Indonesia, including OVO, DANA, ShopeePay, and LinkAja. + +#### E-wallet Payment Details + +|E-wallet Provider|E-wallet Code|Allowed Expiration Time|Flow Type|Payments via desktop|Payments via mobile browser|Payments via provider’s mobile app| +| :- | :- | :- | :- | :-: | :-: | :-: | +|ShopeePay|shopeepay\_ewallet|1 - 60 minute(s)|Redirection (JumpApp)|❌|❌|✅| +|OVO|ovo\_ewallet|55 seconds|Push notification|❌|❌|✅| +|DANA|dana\_ewallet|1 - 60 minute(s)|Redirection (JumpApp)|✅|✅|✅| +|LinkAja|linkaja\_ewallet|5 minutes|Redirection (JumpApp)|❌|❌|✅| + +The maximum amount per transaction for all e-wallet providers is Rp 10,000,000 for customers who have performed KYC on the provider’s app and Rp 2,000,000 for customers who have not. + +#### Feature Availability + +|E-wallet Provider|Refund Feature|Account Linking Feature|Linking Expiry Time|Linking Renewal| +| :- | :- | :- | :- | :- | +|ShopeePay|Full|Supported|5 Years|After Expiry Time| +|OVO|Not Supported|Not supported|-|-| +|DANA|Full, Partial|Supported|10 Years|After Expiry Time| +|LinkAja|Full|Not supported|-|-| + +#### Product Availability + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
E-Wallet ProviderPayment LinkVA AggregatorAPI E-Wallet AggregatorPayment Routing
Single Payment-Single PaymentSingle PaymentDirect Payment
ShopeePay
OVO
DANA
LinkAja
+ +#### Payment Type & Flow +##### Single Payment +Single payment is a type of payment that allows your customer to complete payments using e-wallet easily. + +There are two types of payment flow for Single Payment: Redirection (JumpApp) or Push Notification. + +![E-wallet Redirection Flow](images/acceptingPayments/payment-methods/e-wallet/payment-flow-single-payment-redirection.webp) + +![E-wallet Push Notification Flow](images/acceptingPayments/payment-methods/e-wallet/payment-flow-single-payment-push-notification.webp) + +##### Direct Payment +Direct payment requires account linking, meaning that your customer must connect their e-wallet account to your system before completing payments. Direct payments offer a more seamless payment experience, as your customer does not need to open or get redirected to the e-wallet provider application to complete payments. + +Direct Payments offers both payment with and without authorization (auto-debit). Direct Payment with authorization requires the customer to input a PIN or OTP on every transaction, while Direct Payment without authorization allows your system to deduct your customers balance without the need to enter a PIN or OTP. Direct payment without authorization is suitable for subscription use cases. + +![E-wallet Direct Payment Flow](images/acceptingPayments/payment-methods/e-wallet/payment-flow-direct-payment.webp) + +#### Activation +In order to accept payments using e-wallets, you need to register your merchant to the e-wallet providers first. You can do the registration via OY! Dashboard by opening the Payment Method page, Payment Method - OY! Dashboard. OY! offers real time registration, so you can directly accept payments once you finish submitting your application. + +Here are the requirements that must be fulfilled in order to submit a registration: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+

+

Requirement

+
E-wallet Provider
ShopeePayLinkAjaDANAOVO
Owner/Shareholder ID CardID Card Number
Full Name
Date of Birth
Occupation
Gender
Province
City
District
Village
Postal Code
Full Address
Owner/Shareholder Email Address
Owner/Shareholder Phone Number
NPWPNPWP Owner Type
NPWP Number
Document (in .jpg or .png)
Province
City
District
Village
Postal Code
Full Address
Person in Charge (PIC) - Non DirectorFull Name
+

Position

+

+

+
Email Address
Phone Number
Does your business license have a validity?
Payment Flow
Business Logo (in Google Drive URL format)
Website URL
Business Permit Number (According to the legality document)
Business Identification Number / Company Registration Certificate
Date of Business Establishment (According to the legality document)
Place of Business Establishment (According to the legality document)
Projected turnover per month using E-Wallet
Projected number of transactions per month using E-Wallet
+ + +#### Payments +ShopeePay (Single Payment) + +[Click here to see the payment journey](https://drive.google.com/file/d/162WY4oEKwEcvgF7p_1lQ6VLHS4XGS-yI/view?usp=drive_link) + +DANA + +[Click here to see the payment journey](https://drive.google.com/file/d/1c65gLG1gZdGKWhM6FazlqRA5qJVRhOYf/view?usp=drive_link) + +LinkAja + +[Click here to see the payment journey](https://drive.google.com/file/d/1bN4-fVS0i1ygK96UVo7XcdnIhhQqzZi3/view?usp=drive_link) + +OVO + +[Click here to see the payment journey](https://drive.google.com/file/d/1b_ImuHTQPGF1UE6jrtxhqRAShpbx8DDi/view?usp=sharing) + +#### Simulate Payments +To get more understanding of e-wallet transactions behavior, you can simulate e-wallet Demo transactions that are created in the Demo environment. Here are the steps to simulate e-wallet payments via OY! Dashboard: + +1. Open OY! Dashboard and navigate to the Demo environment +1. Open the “Settings” menu and click “E-wallet Callback”. +1. Insert the payment detail of the transaction you want to simulate: + 1. Choose the e-wallet provider: Shopeepay, DANA, LinkAja, or OVO. + 1. Insert the Transaction Reference Number. For e-wallet transactions that are created via API E-wallet, you can find the Ref Number inside “E-Wallet API menu”. Please look at the image below to guide you + 1. Input the amount of transaction +1. Once you input all the fields, you can simulate the payment by clicking “Send Callback”. If the payment is successful, a success notification will be shown inside the dashboard. OY! will also send the callback to your specified callback URL. If for some reason you did not receive any callback, please contact our customer service to help you solve the problem. + +![E-wallet Simulate Callback](images/acceptingPayments/payment-methods/e-wallet/simulating-callback.webp) +![E-wallet See Reference Number](images/acceptingPayments/payment-methods/e-wallet/preview-dashboard.webp) + +Specifically for Payment Link transactions, you can directly simulate the payment on the Payment Link by clicking “Bayar Tagihan''. + +![E-wallet Simulate Callback Payment Link](images/acceptingPayments/payment-methods/e-wallet/simulating-callback.webp) + +### Cards +#### Introduction +OY! offers both debit and credit cards as a payment method for your customers. We currently accept VISA, Mastercard, and JCB. + +#### Feature Details +1. Transactions are protected by 3DS – protecting you and your customers from fraudulent transactions +1. Support multiple global network (Visa, Mastercard, JCB) – enabling you to process local and overseas transactions + +To increase the chance of successful transactions, please ensure that your customers: + +1. Have enabled 3DS as an authentication method for each transaction made on their card +1. Have sufficient balance or credit limit for each transaction +1. (Especially for overseas transactions) Have notified their card issuer (i.e. the bank or entity that issues the card) of their intention to conduct overseas transactions + +Minimum transaction amount with cards is Rp 15.000. There is no maximum transaction limit for each transaction. The limit depends on the availability of balance or credit limit for each card. + +#### Understanding Overseas Transactions + +Your customers may use credit and/or debit cards issued locally or internationally. If you plan to conduct overseas transactions, it is important to note that OY! can only create the transactions in IDR. This means that your customers can still use their overseas cards for payment, however the card will still be charged in IDR and settlement to your OY! balance will also be done in IDR. The cardholder's billing statement, however, will show the transaction amount in their local currency with foreign exchange rate & extra fees (if any) as applied by their issuer (i.e. the bank or entity that issues the card). + +Some card issuers might not allow overseas transactions. Therefore, it is recommended for your customers to check with their issuing bank regarding country restrictions to reduce the chance of the transaction being declined by the issuer. + +#### Transactions Declined by Issuer + +When a transaction attempt is submitted to your customer’s issuer (i.e. the bank or entity that issues the card), they usually have an automated system and parameters that help them in deciding whether or not to authorize the transaction. The parameters may include, but not limited to, behavior from past transactions, card details such as expiration date and CVV, and availability of funds. + +Even though all of the card details seem correct, the funds are available and 3DS has been enabled for the card, it is possible that the transaction is declined by the issuer. Unfortunately, sometimes the decline reason provided by the issuer is too “generic”. If that’s the case, you may ask your customers to either use alternative cards or payment methods or to contact their issuer directly for more information on the decline reason. Due to privacy & security concerns, issuers can only discuss the specific reason why a transaction is declined to the respective cardholder. This means that issuer will most likely not entertain decline explanation requests via OY!. + +#### Payment Flow + +Flow via Payment Link + +![Cards Flow via Payment Link](images/acceptingPayments/payment-methods/cards/payment-flow-via-payment-link.webp) + +Flow via Payment Routing + +![Cards Flow via Payment Routing](images/acceptingPayments/payment-methods/cards/payment-flow-via-payment-routing.webp) +#### Activation + +If you need to accept payments from your end-users via debit and/or credit cards, you may contact your Business Representative for more information and assistance on the activation process. + +|Requirement |Credit and Debit Cards| +| :-: | :-: | +|Partner Eligibility Form|✅| +|Partner Request Form to Acquiring Bank |✅| +|Partner and OY! Service Agreement |✅| +|Company Profile |✅| +|Operational license (if applicable)|✅| +|KYC Flow |✅| +|List of users|✅| + + +#### Simulate Payments +1. Open OY! Dashboard and and navigate to the Demo environment +1. Create a Payment Link transaction. Make sure you select “Cards” as one of the available payment method options +1. Open the payment link URL on your browser +1. Choose and confirm “Credit/Debit Card” as your payment method. You will then automatically be redirected to a page to fill your card details. If you are not automatically redirected, then click “Payment with Cards” button +1. Fill the card details as follow + 1. Card Number: 2223000000000007 + 2. Card Expiry Time: 01/39 + 3. Card CVN: 100 + 4. Cardholder Name: Testing +1. Fill the email and phone number accordingly +1. Click to pay +1. You will be directed to a page to mock the 3DS step (i.e. in the actual payment in production environment, you will receive an OTP sent to the phone number registered to your card by your issuing bank). Choose from the dropdown menu to successfully authenticate the transaction (note: you may also choose to simulate rejected transactions by selecting unsuccessful authentication from the dropdown). +1. Transaction will be successful + + +## Payment Link +Payment Link is a pre-built checkout page that allows your business to easily and securely accept payments online. You can share the link to your customers and they can choose various payment methods that OY! supports inside the Payment Link. OY! supports up to 17 payment methods– including Bank Transfer, E-Wallet, QR Code (QRIS), Credit and Debit cards. Payment Links can be created without using any code/integration. However, if you need to create Payment Links from your website/application, OY! also provides Payment Link API. + +There are two types of Payment Link: + +|Payment Link Type|Characteristic|Use Case| +| :- | :- | :- | +|One Time|One link to accept single payment|One-Time payments| +|Reusable|One link to accept multiple payments and support lifetime payments|Top Up, Donation| + +![Payment Link Preview](images/acceptingPayments/payment-link/flow/preview-payment-link.webp) + +### Flow + +![Payment Link One Time Flow](images/acceptingPayments/payment-link/flow/payment-flow-one-time.webp) +![Payment Link Reusable Flow](images/acceptingPayments/payment-link/flow/payment-flow-reusable.webp) + +### Features +#### Customize Payment Link transaction configuration +Set your Payment Link configurations depending on the use case of your transactions. There are several things that you can configure: + +1. List of Available Payment Methods + - Set up the list of available payment methods that you allow for your customers. The payment methods available are Bank Transfer (via Virtual Account and Unique Code), Cards (Credit Card/Debit Card), E-Wallet, and QRIS +1. Amount type + - Open Amount: Accept payments of any amount, or up to specified amount + - Closed Amount: Only accept payments of the specified amount +1. Admin Fee type: + - Include Admin Fee: Admin fee will be deducted from the amount once the fund is settled to your balance. Admin fee borne by merchant. + - Excluded from total amount: Admin fee will be added to the customer's total payment. Admin fee borne by the customer. + Total amount to be paid by the customer = Specified Amount + Admin Fee. +1. Payment Link Expiration Time + - By default, Payment Link Expiration Time is 24 hours. You can customize the Payment Link Expiration Time by days and/or hours. The maximum expiration time is 31 days + 23 hours. + - Specifically for Reusable Payment Link, you can set the expiration time to “Lifetime”, meaning that the link does not have an expiration time and can accept payments any time unless the Payment Link is manually deactivated. + +You can define your default configuration, so you will no longer need to fill in these fields again the next time you create a payment link. Save your default configuration by ticking "Use this configuration for future transaction(s)” option when creating Payment Links. + +![Payment Link Save Configuration](images/acceptingPayments/payment-link/features/default-config.webp) + +#### Monitoring transactions via OY! Dashboard +All created Payment Link transactions are shown in the OY! Dashboard. Navigate to “Payment Link” → “One Time”/”Reusable” to see the list of created transactions. Inside the dashboard, you can see the details of the transactions, including all the transaction information inputted during creation, the status of transactions, and the payment reference number\*. The dashboard also has a feature to search, filter, and export the list of transactions in various formats: Excel (.xlsx), PDF (.pdf), and CSV(.csv) + +![Payment Link Monitoring Transactions](images/acceptingPayments/payment-link/features/dashboard-view.webp) + +\*Payment Reference Number is an identifier of a payment attempt when the customer successfully completes a QRIS payment. The reference number is also stated in the customer’s receipt/proof of transaction. Only available for QRIS transactions. + +#### Customize theme +By default, Payment Link uses OY’s default theme. However, the default theme might not suit your brand personality. In order to maintain a consistent brand experience for your users, you can customize the look and feel of your Payment Link, where you can do the following things: + +1. Upload a custom logo +1. Choose the theme colors +1. Choose the button colors + +If you use OY’s Account Receivable product, the customization of Payment Link also applies to the Account Receivable, and vice versa. + +You can customize the theme for Payment Link through OY! Dashboard. Here are the steps to guide you in doing so: + +1. Log onto OY! Dashboard via Desktop (). The menu is currently unavailable in the mobile web version and OY! Business App. +1. Go to the “Settings” menu and click “Payment Link Display”. +1. To customize the logo in the Payment Link, you must input the logo in a URL format (). + - If you do not have the URL for your logo, you can use online tools like [snipboard.io](https://snipboard.io/) or [imgbb](https://imgbb.com/). + - Once you convert your logo to URL, the correct URL should look like this: + - Snipboard.io: + - Ibbmg: +1. To change the header color in the Payment Link, you can choose a color from the Color Picker or you can type the HEX color code in “Header Color” (e.g. #FFFFFF). +1. You can choose a different color for the buttons inside the Payment Link. Choose a color from the Color Picker or type the HEX color code in “Button & Link Color”. +1. Save the changes. The changes will be applied to all payment links created in real-time. You can see when the last Payment Link Display was modified. + +![Payment Link Customizing Theme](images/acceptingPayments/payment-link/features/dashboard-customized-payment-link.webp) + +Here is the example of the Payment Link before and after it is customized. The header’s color is customized using brown color and the button’s color uses green color. + +![Payment Link Customized](images/acceptingPayments/payment-link/features/payment-link-after-changes.webp) + +#### Share Payment Link to multiple channels +You can share created Payment Link directly to your customers through multiple channels, including email, WhatsApp message, and copy link + +***Email***
+Send created Payment Link up to 6 email recipients per Payment Link. This feature is supported for creation via OY! Dashboard and API. Sending Payment Links via email is free of charge. + +You can fill the email recipients in the “Email(s)” field under “Customer Detail” section when creating Payment Links via OY! Dashboard. Separate multiple emails using semicolon (;) characters. Example: ;email2@company.com;email3@company.com + +If you use Payment Link API, you can insert the email recipients under the “customer\_email” parameter when creating Payment Links. Please refer to the [Payment Link Creation](https://api-docs.oyindonesia.com/#api-create-payment-link-fund-acceptance) section in the API Docs for further details. + +***WhatsApp***
+Send Payment Link to unlimited WhatsApp account per Payment Link. This feature is supported for creation via API only, and not supported for creation via OY! Dashboard. By default, this feature is not automatically activated after registration. Please contact your business representative to activate this feature + +Once you successfully create Payment Links, hit our API Send WhatsApp to send created Payment Links to your customers. Please refer to [API Send WhatsApp](https://api-docs.oyindonesia.com/#api-send-whatsapp) section for further details + +Your customers will receive a WhatsApp message from OY’s WhatsApp account using this format: + +Hi {{Customer Name}},
+Anda memiliki transaksi di {{Your Brand Name}} yang sedang menunggu pembayaran. Lakukan pembayaran sebelum {{Payment Link Expiration Time}}. +Silakan klik link berikut untuk membayar: {{Payment Link URL}} + +Mohon untuk tidak membalas pesan ini. + +Example: +Hi John Doe,
+Anda memiliki transaksi di Jane’s Store yang sedang menunggu pembayaran. Lakukan pembayaran sebelum 1-Feb-2022, 13.28. +Silakan klik link berikut untuk membayar: + +Mohon untuk tidak membalas pesan ini. + +***Copy Link***
+After creating Payment Links, you will receive a URL link that can be copied and shared to your customers + +If you create the Payment Link via OY! Business App, you can use the built-in share feature from your mobile device when sharing Payment Link. + +![Payment Link Sharing Capabilities](images/acceptingPayments/payment-link/features/copy-link.webp) + +#### Receipt for successful payments +Customers can directly see the receipt of payments inside the Payment Link once payment is made. Customers can also receive the receipt via email(s) that you provided during Payment Link creation. Configure sending receipt via emails to your customers by going through this steps: + +1. Log in to your OY! Dashboard account +1. Go to “Settings” → “Notifications” +1. Click “Receive Money (To Sender)” +1. Choose “Enable Success Notification” for Payment Link and/or Invoice Link +1. Input your logo to be put on the email in URL format () + 1. If you do not have the URL for your logo, you can use online tools like [snipboard.io](https://snipboard.io/) or [imgbb](https://imgbb.com/). + 1. Once you convert your logo to URL, the correct URL should look like this: + 1. Snipboard.io: + 1. Ibbmg: +1. Save the changes by clicking “Save” +1. Create a Payment Link transaction and input the customer’s email address in the “Email(s)” field under “Customer Detail” section when creating Payment Links via OY! Dashboard. Separate multiple emails using semicolon (;) characters. Example: ;email2@company.com;email3@company.com +1. Your customer will receive successful receipt to the emails once payment is made + +![Payment Link Notifications to Payer](images/acceptingPayments/payment-link/features/dashboard-notif-for-sender.webp) + +Note: If you do not put any of your customer’s email during Payment Link creation, OY! will not send any receipt via email even though you enabled the notification configuration + +You can also receive the receipt to your email once payment is made by the customer. Configure sending receipt via emails to your customers by going through this steps: + +1. Log in to your OY! Dashboard account +1. Go to “Settings” → “Notifications” +1. Click “Receive Money (To Receiver)” +1. Choose “Enable Success Notification” for Payment Link and/or Invoice Link +1. You can fill up to 3 email recipients. Separate the emails using semicolon (;) characters. Example: ;email2@company.com;email3@company.com +1. Save the changes by clicking “Save Changes” +1. You will receive an email for every successful Payment Link payments made by your customers + +![Payment Link Notifications to Merchant](images/acceptingPayments/payment-link/features/dashboard-notif-for-receiver.webp) + +#### Embedding Payment Link to your website/application +Create a seamless payment experience for your customers by inserting a created Payment Link to your website or application. Customers can stay on your page without being redirected to another page to complete the payments. There are many ways that you can show a Payment Link inside your page, and here are several suggestions that you can use: Pop Up - Center, Pop Up - Right, Pop Up - Left, and Slide - Right. Please refer to [Embed Payment Link](https://api-docs.oyindonesia.com/#pop-seamless-payment-experience-fund-acceptance) in the API Docs to see detailed implementation of inserting Payment Link to your application. +#### Refund payments to customer +When your customer receives a defective product or the product is not delivered, they might request to refund the payment. You can directly refund payments to your customer’s account via OY! Dashboard. A refund can either be full or partial. A full refund gives your customers the entire amount back (100%). A partial refund returns the amount partially. + +Refunds are free of charge. However, the admin fee charged for the original payment is not refunded by OY! to your balance. + +There are several requirements that must be met to issue a refund: + +1. Refunds can only be issued up to 7 calendar days after the transaction is marked as successful. +1. You have enough balance that allows us to deduct the amount of the transaction that should be refunded. +1. A refund can only be issued once for each successful transaction, whether it is a full or partial refund. +1. Refunds must be issued during operational hours, depending on the payment method. Refer to the table below. + +Currently, refunds are only available for e-wallet payments. + +|Payment Method|Refund Feature|Operational Hours| +| :- | :- | :- | +|

DANA

|Full Refund, Partial Refund|00\.00 - 23.59 GMT+7| +|ShopeePay|Full Refund|05\.00 - 23.59 GMT+7| +|LinkAja |Full Refund|00\.00 - 23.59 GMT+7| +|OVO|Not supported|-| + +Here are the steps to do refunds for Payment Link transactions: + +1. Log in to your OY! Dashboard with your username and password that you registered with. +1. Open “Payment Link” menu and choose “One Time”/“Reusable”, depending on your transactions +1. Search record of the transaction that wants to be refunded. Under the “Action” column, you can click the three-dots button and click “Refund E-Wallet” to issue a refund. +1. If the refund does not meet the requirement above, an error message will show up and you can not continue the refund process. +1. However, if the transaction is eligible for a refund, a pop up will be shown to continue the refund process +1. For partial refund, you can fill the amount to be refunded +1. Make sure that you have enough balance to issue a refund. If you do not have enough balance, an error message will show up. You can top up your OY’s balance first. +1. Once you successfully issue a refund, the transaction status changes to “Refunded”. +1. You can see the refunded transaction in the “Account Statement” page by clicking “Transaction Report” → “Account Statement”. + +#### Retry notification/callback for successful payments + +If you use Payment Link API, OY! sends a notification/callback to your system once a transaction is marked successful. Therefore, you will be notified if the customer has already completed the payment. There might be a case where your system does not receive the notification successfully. + +By enabling Retry Callback, OY! will try to resend another callback to your system if your system does not receive the callback successfully. You can request to resend a callback using Manual Retry Callback or Automatic Retry Callback. + +##### Manual Retry Callback + +Manual Retry Callback allows you to manually send callbacks for each transaction from OY! Dashboard. Here are the steps to do so: + +1. Login to your account in OY! Dashboard +1. Open “Payment Link” and choose "One Time"/"Reusable" depending on the transaction +1. Search the record of the transaction and click 3 dots button under “Action” column +1. Make sure that you have set up your Callback URL via “Settings” → “Developer Option” → “Callback Configuration” +1. Make sure to whitelist OY’s IP to receive callbacks from OY! + - 54.151.191.85 + - 54.179.86.72 +1. Click “Resend Callback” to resend a callback and repeat as you need + +![Payment Link Manual Retry Callback](images/acceptingPayments/payment-link/features/dashboard-resend-callback.webp) + +##### Automatic Retry Callback +Automatic Retry Callback allows you to receive another callback within a certain interval if the previous callback that OY! sent is not received successfully on your system. OY! will try to resend other callbacks up to 5 times. If your system still does not receive any callbacks after 5 retry attempts from OY, OY! will notify you via email. You can input up to 6 email recipients and it is configurable via OY! Dashboard. + +Callback Interval: Realtime → 1 minute (after the initial attempt)→ 2 minutes (after the first retry attempt)→ 13 minutes (after the second retry attempt) → 47 mins (after the third retry attempt) + +OY! sends the first callback to your system once the transaction is successful on OY!’s side. If your system fails to receive the callback, OY! will send the first retry callback attempt to your system immediately. If your system still fails to receive the callback, OY! will send the second retry callback attempt 1 minute after timeout or getting a failed response from your side. The process goes on until you successfully receive the callback or all retry callback attempts have been sent. + +Automatic Retry Callback is not activated by default. You can see the guideline below to enable Automatic Retry Callback: + +1. Login to your account in OY! Dashboard +1. Go to “Settings” and choose “Developer Option". +1. Choose “Callback Configuration” tab +1. Input your callback URL in the related product that you want to activate. Make sure you input the correct URL format. Please validate your callback URL by clicking “URL String Validation” +1. If you want to activate automated retry callback, tick the Enable Automatic Retry Callback for related products. You must input the email recipient(s) to receive a notification if all the callback attempts have been made but still failed in the end. +1. Make sure to whitelist OY’s IP to receive callbacks from OY + - 54.151.191.85 + - 54.179.86.72 +1. Make sure to implement the idempotency logic in your system. Use “tx\_ref\_number” parameter as idempotency key to ensure that multiple callbacks under “tx\_ref\_number” key should not be treated as multiple different payments. +1. Save the changes + +![Payment Link Automatic Retry Callback](images/acceptingPayments/payment-link/features/dashboard-developer-option.webp) + +#### Multi Entity Management +Multi Entity Management is a feature that can help you handle multiple OY! accounts under one entity. The account that acts as an admin is called a Main Entity and the accounts that can be controlled by the admin are called Sub Entity. + +Using this feature, you are able to accept payments from your customers through Payment Link that is created on behalf of a Sub Entity. When your users make a successful transaction, the transaction will be recorded in the Sub Entity’s balance. As a Main Entity, you are equipped with the ability to view the Sub Entities' balance and transaction list anytime through Multi Entity → Sub Entity Statement. + +Please refer to the [Multi Entity Management](https://docs.oyindonesia.com/#how-to-use-multi-account-management) section in this documentation for detailed information + +### Registration and Setup +Here are the steps to guide you through registration and set up for creating Payment Link transactions. + +1. Create an account at OY! Dashboard +1. Do account verification by submitting the verification form. Ensure to tick the “Receive Money” product since Payment Link is a part of Receive Money products. +1. OY! team will review and verify the form and documents submitted +1. Once verification is approved, set up your receiving bank account information. Important Note: Ensure that the receiving bank account information is accurate as you can only set it up once via OY! Dashboard for security reasons +1. By default, you get several payment methods on the get go, including all Bank Transfers (excl. BCA) +1. Other payment methods like QRIS, Ewallets, and BCA need additional onboarding to be available to use. Please refer to this section for detail guidelines: + - [E-wallet Activation](https://docs.oyindonesia.com/#e-wallet-payment-methods) + - [QRIS Activation](https://docs.oyindonesia.com/#qris-payment-methods) + - [VA BCA Activation](https://docs.oyindonesia.com/#bank-transfer-virtual-account-payment-methods) + +If you use Payment Link API, then you need to do additional steps, including: + +1. Submit your IP address(es) & callback URL to your business representative or send an email to +1. OY! will send the Production API Key as an API authorization through your business representative. + Note: Staging/Demo API Key can be accessed via OY! Dashboard by going to the “Demo” environment and the key can be found on the bottom left menu. +1. Integrate Payment Link API to your system. Please follow the API documentation to guide you through.[ Payment Link - API Docs](https://api-docs.oyindonesia.com/#api-create-payment-checkout) + +Once you completed all the steps above, you are ready to create your Payment Links +### Creating Payment Links +You can create Payment Links via OY! Dashboard. Another way to create a Payment Link is through API, however it is only supported for One Time Payment Link. Here are the guidelines to create Payment Links code-free via OY! Dashboard: + +1. Log in to your OY! Dashboard account +1. To create a real transaction, choose the “Production” environment on the sidebar. However, to create a demo transaction for testing purposes, choose the “Try in Demo” environment. +1. Navigate to “Receive Money” and choose “Payment Link”. Click “One Time”/”Reusable” depending on the type of Payment Link you want to create +1. Click "Create Payment Link" +1. A pop up will be shown for you to fill in the details of the Payment Link. Please refer to the table below to know the explanation of each field. +1. Click “Save” +1. Once a creation is successful, you can see the created Payment Link and share it to your customers. + +![Payment Link Creation](images/acceptingPayments/payment-link/creating-payment-link/creation.webp) + +|Field|Description| +| :- | :- | +|Amount|The amount that will be displayed in the payment link. You must fill this field if you use a closed amount. | +|Description (Optional)|You can describe the payment context to customers through the description. | +|Partner Transaction ID|A unique transaction ID that you can assign to a transaction| +|Balance Destination|

Only available if you use Multi Entity Management.

You can choose between “My Balance” or “My Sub Entity’s Balance”

My Balance: Once a transaction is successful, the fund will be settled to your balance account

My Sub Entity’s Balance: Once a transaction is successful, the fund will be settled to your selected Sub Entity’s balance account.

| +|Customer Detail|Customer details that can be specified (optional): Customer Name, Phone Number, Email, and Notes. We will send the payment link to the specified email address (if email address is filled in)| +|Amount Type|

You can choose between “Open Amount” and “Closed Amount”.

Open Amount: Accept payments of any amount, OR up to specified amount (if Amount is filled during creation ).

Closed Amount: Only accept payments of the specified amount

| +|Payment Method|

The payment method that you can choose to enable/disable for your customers. The payment methods available are Bank Transfer (via Virtual Account and Unique Code), Cards (Credit Card/Debit Card), E-Wallet, and QRIS

| +|Admin Fee Method|

You can choose between "Included in total amount" or "Excluded from total amount".

Included in total amount: Admin fee will be deducted from the amount

Excluded from total amount: Admin fee will be added to the customer's total payment. Total Amount = Specified Amount + Admin Fee

| +|Payment Link Expiration Time|

The expiry time of a Payment Link. Once expired, customers can not open the link anymore.

By default, Payment Link Expiration Time is 24 hours. You can customize the Payment Link Expiration Time by days and/or hours

Specifically for Payment Link Reusable, you can set the expiry time as “Lifetime”, meaning that the link does not have an expiration time and can accept payments any time unless the Payment Link is manually deactivated.

| + +### Completing payments +Once you successfully create a Payment Link, you may share the link to your customers. Customers can open the link via desktop or mobile browsers. Here are the steps for your customers to complete a payment via Payment Link: + +1. Fill or change the amount of transaction (only available for open amount transactions) +1. Choose the desired payment method +1. Fill the customer detail section, including Customer Name, Email, Phone Number, and Notes. All fields are optional except for Customer Name +1. Confirm the payment method to be used by clicking “Pay” +1. OY! will show the payment information for your end users to complete the payment based on the payment method chosen. + - For Bank Transfer transactions, Account Number and the Amount to be transferred will be shown + - For QRIS Transactions, the QR generated from OY! will be shown in the page and can be downloaded or your customer may also directly scan the QR there + - For e-wallet Transactions, the customers can be automatically redirected to the e-wallet’s app (DANA, LinkAja, ShopeePay) or receive notification from the e-wallet’s app (OVO) + - For Credit & Debit Cards, customers will be redirected to fill the card number, card expiry date, and CVV +1. You have to be aware that each Payment Method has a different expiry time to complete payments. Please refer to the table below for information +1. To simulate demo transactions, please refer to these sections: + - [Simulate Virtual Account payments](https://docs.oyindonesia.com/#bank-transfer-virtual-account-payment-methods) + - [Simulate Unique Code payments](https://docs.oyindonesia.com/#bank-transfer-unique-code-payment-methods) + - [Simulate E-wallet payments](https://docs.oyindonesia.com/#e-wallet-payment-methods) + - [Simulate Cards payments](https://docs.oyindonesia.com/#cards-payment-methods-payment-methods) + - Note: Simulate QRIS transactions is currently not available +1. The status on the Payment Link will change to successful once the payment is made. Customers can do a check status on the Payment Link page in case the transaction status is not automatically updated + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Payment MethodExpiry Time*
Bank TransferVirtual AccountUp to 24 hours
Unique CodeUp to 3 hours
E-WalletShopeePayUp to 60 minutes
LinkAja5 minutes
DANAUp to 60 minutes
OVOUp to 55 seconds
QRISUp to 30 minutes
Credit and Debit Cards60 minutes
+ +\*Payment method expiration time is different from Payment Link expiration time. Payment method expiration is counted from when the customer confirms a payment method, meanwhile Payment Link expiration time is counted from when the Payment Link is created. You can only customize Payment Link expiration time. + +Example: You create a Payment Link that accepts Virtual Account (VA) and QRIS as Payment Method and set the Payment Link expiration time to 2 hours. Your customer opens the Payment Link and chooses QRIS as the payment method. OY! will generate the QR and the QR can be paid within the next 30 minutes. After 30 minutes, the QR expires and your customer must choose another payment method. This time, your customer chooses VA as payment method. OY! will generate the VA number and the VA validity is 1 hour and 30 minutes because the remaining Payment Link expiry time is 1 hour and 30 minutes. After 2 hours, your customer is not able to open the Payment Link since the Payment Link has expired. + +### Checking transaction status +All created Payment Link transactions are shown in OY! Dashboard. Navigate to “Payment Link” → “One Time”/”Reusable” to see the list of created transactions. Inside the dashboard, you can see the details of the transactions, including all the transaction information inputted during creation, status of transactions, and the payment reference number. The dashboard also has a feature to search, filter, and export the list of transactions in various formats: Excel (.xlsx), PDF (.pdf), and CSV(.csv) + +There might be times that your customer already completed the payments but the transaction status is not updated to success. In this case, there are several ways to check the status of a transaction: + +1. Customers can directly check the status of the transaction by clicking “Check Status” button on the Payment Link page +1. You can check the status of the transaction by hitting API Check Status. Please refer to this section in the API Docs. [Check Status Payment Link - API Docs](https://api-docs.oyindonesia.com/#api-payment-status-fund-acceptance). + +### Receiving fund to balance +Once a transaction is paid by the customer, OY! updates the transaction status and sends notification to your system indicating that the transaction has been paid, and settles the funds to your OY! balance. Each payment method has a different settlement time, varying from real-time to D+2 Working Days. + + +## VA Aggregator +Businesses are struggling to manage hundreds or even thousands of physical bank accounts that are used for different purposes. It causes significant overhead costs in terms of the amount of account maintenance and manhours needed for reporting and reconciliation purposes, combining different information from different accounts. + +Virtual Account (VA) is essentially a dummy account that is linked to a physical account and has all the physical account characteristics that enable a much easier reporting and reconciliation process by centralizing the money flow into the physical account. By issuing VAs, you can assign each VA for a specific person and/or purposes. + +Virtual Account (VA) Aggregator is a feature that is specifically designed to generate Virtual Account that enable you to receive payments from your end-users via virtual account (VA) bank transfer. If you intend to use multiple payment methods to receive payment for one transaction, you should consider Payment Link and Payment Routing instead. + +Generally, you may create a VA number for your customers via API VA Aggregator. However, if you prefer to create VA without API integration, then you may do so via OY! Dashboard by clicking the Virtual Account (Created VA) tab under the “Receive Money” section. + +### Flow +![VA Aggregator Flow](images/acceptingPayments/va-aggregator/payment-flow.webp) + + +### Features +1. Flexible creation – either via Dashboard or API + - You can create the VA number either by OY! Dashboard or API. Don’t worry if you don’t have the resources to conduct API integration since you can still create VA number and receive payment from your customers through OY! Dashboard +1. Support VA payments from multiple banks. Currently we support VA payments from 8 banks: + 1. Bank Central Asia (BCA) + 1. Bank Rakyat Indonesia (BRI) + 1. Bank Mandiri + 1. Bank Negara Indonesia (BNI) + 1. Bank CIMB & CIMB Syariah + 1. Bank SMBC + 1. Bank Syariah Indonesia (BSI) + 1. Bank Permata & Permata Syariah +1. Quick settlement for majority of the banks + - We understand that you need the funds to be as quickly as possible to be settled to you. We offer real-time settlement for majority of the banks listed so you should not worry about your cashflow. +1. Customizable VA types + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
CategoryFeatureDescription
Validity PeriodStatic (Lifetime) VA that has a lifetime validity. It will always be active and available to receive payment until it is manually deactivated.
Dynamic VA that has a specific validity period. It will always be active until it is expired or manually deactivated.
Usage FrequencySingle UseVA that expires after a single payment. A single-use configuration can only be set up for a dynamic VA.
Multiple UseVA that only expires when the expiration date is reached or when it is manually deactivated. You may also customize the limit of maximum payment. VA Multiple Use with customized maximum payment number will expire after the payment number limit is exceeded even if it has not reached the expiration time yet.
AmountClosed AmountVA that only accepts payment of a specific amount as set when you create the VA number.
Open AmountVA that accepts payment of any amount. You do not need to specify the amount when creating the VA number.
VA NumberCustomizable

You may personalize the VA suffix using the numbers you want (e.g. your end-users' phone number or billing number). To enable VA number customization, please contact your business representative. You may refer to the API Docs - Create Customized VA Number

Specifically for this feature, we currently only support BRI and CIMB.

PredeterminedOY! will create the VA number combination on your behalf. You may refer to the API Docs - Create VA Number +
+ +5. Capability to Update VA + 1. After you have created the VA number, you are still able to modify the parameters below: + - VA Amount (amount) + - Multiple Use / Single Use (is\_single\_use) → you may update a single use VA to be a multiple use VA and vice versa + - Email (email) + - Transaction Counter (trx\_counter) → you may update the number of payments a VA number can accept + - Transaction Expired Time (trx\_expired\_time) → the expiration time of a transaction of a VA number + - Expired Time (expired\_time) → the expiration time of a VA number. The VA expiration time must be at least equal or greater than the transaction expiration time + - Username Display (username\_display) → the VA name that is displayed when your customer inputs the VA number in their mobile/internet banking application + 1. Once a VA is updated, the new set of configuration will apply for that VA and the previous configuration is no longer applicable +1. Automatic Callback & Retry Callback + - You will get a callback for each successful VA payment from your customers via API. In addition, you may activate our “Enable Automatic Retry Callback” via OY! Dashboard (Settings → Developer Option → Callback Configuration tab). By activating this feature, if on the first try the callback is not successfully received by your system, then OY! system will automatically retry the callback delivery until 5 attempts. If all callback attempts still returned failed, OY! system will send email notification to email address that has been set in your configuration. + - You will also get a callback for each successful transaction fund settlement to your OY! balance +1. Minimum & Maximum Amount + - Minimum amount for each VA transaction is Rp 10,000 (for closed amount) + - Maximum amount for VA transactions depends on the banks: + + +|Banks|Max. Amount per transaction | +| :-: | :-: | +|Bank Central Asia (BCA) |Rp 50,000,000| +|Bank Negara Indonesia (BNI) |Rp 50,000,000| +|Bank Rakyat Indonesia (BRI) |Rp 500,000,000| +|Bank Mandiri |Rp 500,000,000| +|Bank CIMB |Rp 500,000,000| +|Bank SMBC |Rp 100,000,000| +|Bank Syariah Indonesia (BSI)|Rp 50,000,000| +|Bank Permata|Rp 500,000,000| + + +### Use Cases + +![VA Aggregator Use Case](images/acceptingPayments/va-aggregator/use-cases.webp) + +### Registration and Set Up +Here are the procedures to activate VA Aggregator feature: + +1. Create an OY! account +1. Do account verification by submitting the verification form. Ensure to tick the “Receive Money” product since VA Aggregator is a part of Receive Money products. +1. OY! team will review and verify the form and documents submitted +1. Once verification is approved, set up your receiving bank account information. Important Note: Ensure that the receiving bank account information is accurate as you can only set it up once via OY! Dashboard for security reasons +1. You may need to submit additional documents to be able to use VA for BCA (including, but not limited to, Taxpayer Registration Number (NPWP), and Nationality ID) +1. If you have any questions or concerns during this process, feel free to contact your business representative or email + + +If you plan to use VA Aggregator by API, then you need to do additional steps: + +1. Submit your IP address(es) & callback URL to your business representative or send an email to [business.support@oyindonesia.com](mailto:business.support@oyindonesia.com). The maximum number of IP addresses that can be registered are 5 addresses. +1. OY! will send the Production API Key as an API authorization through your business representative. + Note: Staging/Demo API Key can be accessed via OY! Dashboard by going to the “Demo” environment and the key can be found on the bottom left menu. +1. Integrate API VA Aggregator to your system. Please follow the API documentation to guide you through[ ](https://api-docs.oyindonesia.com/#api-create-payment-checkout)[API Docs - VA Aggregator](https://api-docs.oyindonesia.com/#create-customized-va-va-aggregator) + + +### Testing +1. Create VA Number via API +1. After creating an account, log on to OY! Dashboard and click “Try in Demo” button that will redirect you to our staging environment +1. Scroll down to the bottom left of the navigation bar and copy the API staging key for your perusal +1. Using the API staging key, create a VA number by sending a POST request to . Enter the required parameters stated in the API Docs +1. OY! system will respond to your request with a created VA number +1. Create VA via Dashboard + 1. After creating an account, log on to OY! Dashboard and click “Try in Demo” button that will redirect you to our staging environment + 1. Scroll down to “Receive Money” tab → Virtual Account → Created VA + 1. Click the top right button “Create Virtual Account” + 1. You may choose to create the VA number(s) by uploading an Excel file (with the format as the template) or by inputting the VA number manually (in this case, you may click the “Add Virtual Account Details Manually” button) + 1. After you have successfully uploaded the file or filled in the fields (if manual), click “Validate” button on the bottom right corner + 1. After you have successfully validated your entries, click “Submit” button on the bottom right corner + 1. Once you have successfully submitted your request, you will be redirected to Created VA page where you can see your newly created VA numbers + 1. Your created VA numbers should be ready to use +1. Simulating Successful Callback + 1. To simulate a successful payment, ensure that you are in our staging environment. Click “Try in Demo” button that will redirect you to our staging environment + 1. Scroll down to “Settings” tab → Callback Bank Transfer + 1. Choose the “Virtual Account” as the Transaction Type + 1. Select the Bank Name of the VA number that you have previously created + 1. Enter the VA number and amount. For Closed VA, you need to enter the exact amount of the VA as created + 1. Enter the payment date and time. Ensure that payment date and time are greater than created but less than expiration time + +### How to Use +1. Viewing list of Created VA + 1. You can monitor your created VA numbers through the “Receive Money” tab → Virtual Account → Created VA + 1. You can also see their payment status, amount, VA type, and count of completed transactions. You may also click to export these details to your device as PDF, Excel or CSV + ![VA Aggregator Monitor Created Transactions](images/acceptingPayments/va-aggregator/viewing-list-of-created-va.webp) +1. Viewing list of VA Payment + 1. For all successful VA transactions, you can monitor them through the “Receive Money” tab → Virtual Account → Incoming Payment + 1. You can also see the transaction timestamp, status, amount, admin fee and other information. You may also click to export these details to your device as PDF, Excel or CSV + ![VA Aggregator Monitor Incoming Transactions](images/acceptingPayments/va-aggregator/viewing-list-of-incoming-payment.webp) + + +### VA Bank Details +Capabilities + + +|Banks|Bank Code|Open Amount |Closed Amount |Max. Expiration Time| +| :-: | :-: | :-: | :-: | :-: | +|Bank Central Asia (BCA) |014|Yes|Yes|Lifetime| +|Bank Negara Indonesia (BNI) |009|Partial*|Yes|Lifetime| +|Bank Rakyat Indonesia (BRI) |002|Yes|Yes|Lifetime| +|Bank Mandiri |008|Yes|Yes|Lifetime| +|Bank CIMB |022|Yes|Yes|Lifetime| +|Bank SMBC |213|Yes|Yes|Lifetime| +|Bank Syariah Indonesia (BSI)|451|No|Yes|70 days after creation| +|Bank Permata|013|Yes|Yes|Lifetime| + +*contact our business representative for further information + +Note: there is no minimum expiration time for VAs. However, you are recommended to set a reasonable expiration time, enabling your customers to complete their payments conveniently. + + +### Available Payment Channels for VA + +Your end-users may use the below payment channels to pay for their bills via VA + + +| Bank (Virtual Account) | SKN | RTGS | ATMs | Intrabank Mobile Banking & Internet Banking | Interbank Internet Banking | Interbank Mobile Banking | +| ---------------------- | --- |---- |---- | ------------------------------------------- | --------------------------| ---------------------------- | +| Bank Mandiri | Yes | Yes | Yes | Yes | Yes | Yes | +| BRI | Yes | Yes | Yes | Yes | No | Yes | +| BNI | Yes | Yes | Yes | Yes | No | Yes | +| Permata | Yes | Yes | Yes | Yes | No | Yes +| CIMB Niaga / CIMB Niaga Syariah | Yes | Yes | Yes | Yes (Mobile Banking), No (Internet Banking)| No | Yes | +| BCA | No | No | Yes | Yes | No | No | +| SMBC | Yes | No | Yes | Yes (Mobile Banking), No (Internet Banking) | No | Yes | +| BSI | No | No | Yes | Yes | Yes | Yes | + + + + + +## API E-Wallet Aggregator +E-Wallet API allows clients to charge and receive payments directly from Indonesia's top e-wallet providers. With one integration, they are able to get access to all of OY’s available e-wallets. + +### Flow + +![E-wallet Aggregator Flow](images/acceptingPayments/api-e-wallet-aggragator/payment-flow.webp) + +### Features +#### Support multiple E-wallets +Our E-wallet Aggregator product support e-wallet transactions from these issuers: + +- ShopeePay +- LinkAja +- DANA +- OVO + +#### Monitor transactions via OY! Dashboard +All created e-wallet transactions are shown in OY! Dashboard. Navigate to “E-Wallet” to see the list of transactions. Inside the dashboard, you can see the details of the transactions, including all the transaction information inputted during creation, status of transactions, and the payment reference number. The dashboard also has a feature to search, filter, and export the list of transactions in various formats: Excel (.xlsx), PDF (.pdf), and CSV (.csv) + +![Monitor E-wallet Aggregator Transaction](images/acceptingPayments/api-e-wallet-aggragator/dashboard-selesai.webp) + +#### Receipt for successful payments +Customers can receive receipt of successful payments via email(s) that you provided during the creation process. Configure sending receipt via emails to your customers by going through these steps: + +1. Log in to your OY! Dashboard account +1. Go to “Settings” → “Notifications” +1. Click “Receive Money (To Sender)” +1. Choose “Enable Success Notification” for E-Wallet API +1. Input your logo to be put on the email in URL format () + - If you do not have the URL for your logo, you can use online tools like [snipboard.io](https://snipboard.io/) or [imgbb](https://imgbb.com/). + - Once you convert your logo to a URL, the correct URL should look like this: + - Snipboard.io: + - Ibbmg: +1. Save the changes by clicking “Save” +1. Create an E-Wallet transaction via API and input the customer’s email address in the “email” parameter. +1. Your customer will receive successful receipt to the emails once payment is made + +![Receipt for successful Payment](images/acceptingPayments/payment-link/features/dashboard-notif-for-sender.webp) + +Note: If you do not put any of your customer’s email during transaction creation, OY! will not send any receipt via email even though you enabled the notification configuration +#### Retry notification/callback for successful payments +OY! will send a notification/callback to your system once a transaction is marked successful. Therefore, you will be notified if the customer has already completed the payment. There might be a case where your system does not receive the notification successfully. + +By enabling Retry Callback, OY! will try to resend another callback to your system if your system does not receive the callback successfully. You can request to resend a callback using Manual Retry Callback or Automatic Retry Callback. + +##### Manual Retry Callback +Manual Retry Callback allows you to manually send callbacks for each transaction from OY! Dashboard. Here are the steps to do so: + +1. Log in to your account in OY! Dashboard +1. Navigate to “E-Wallet API ” +1. Search the record of the transaction and click 3 dots button under “Action” column +1. Make sure that you have set up your Callback URL via “Settings” → “Developer Option” → “Callback Configuration” +1. Make sure to whitelist OY’s IP to receive callbacks from OY! + - 54.151.191.85 + - 54.179.86.72 +1. Click “Resend Callback” to resend a callback and repeat as you need + +![Manual Retry Callback](images/acceptingPayments/api-e-wallet-aggragator/dashboard-callback.webp) + +##### Automatic Retry Callback +Automatic Retry Callback allows you to receive another callback within a certain interval if the previous callback that OY! sent is not received successfully on your system. OY! will try to resend other callbacks up to 5 times. If your system still does not receive any callbacks after 5 retry attempts from OY, OY! will notify you via email. You can input up to 6 email recipients and it is configurable via OY! Dashboard. + +Callback Interval: Realtime → 1 minute (after the initial attempt)→ 2 minutes (after the first retry attempt)→ 13 minutes (after the second retry attempt) → 47 mins (after the third retry attempt) + +OY! sends the first callback to your system once the transaction is successful on OY!’s side. If your system fails to receive the callback, OY! will send the first retry callback attempt to your system immediately. If your system still fails to receive the callback, OY! will send the second retry callback attempt 1 minute after timeout or getting a failed response from your side. The process goes on until you successfully receive the callback or all retry callback attempts have been sent. + +Automatic Retry Callback is not activated by default. You can see the guideline below to enable Automatic Retry Callback: + +1. Login to your account in OY! Dashboard +1. Go to “Settings” and choose “Developer Option". +1. Choose “Callback Configuration” tab +1. Input your callback URL in the related product that you want to activate. Make sure you input the correct URL format. Please validate your callback URL by clicking “URL String Validation” +1. If you want to activate automated retry callback, tick the Enable Automatic Retry Callback for related products. You must input the email recipient(s) to receive a notification if all the callback attempts have been made but still failed in the end. +1. Make sure to whitelist OY’s IP to receive callbacks from OY + - 54.151.191.85 + - 54.179.86.72 +1. Make sure to implement the idempotency logic in your system. Use “tx\_ref\_number” parameter as the idempotency key to ensure that multiple callbacks under “tx\_ref\_number” key should not be treated as multiple different payments. +1. Save the changes + +![Automatic Retry Callback](images/acceptingPayments/api-e-wallet-aggragator/dashboard-developer-option.webp) + +#### Refund transactions to customer +When your customer receives a defective product or the product is not delivered, they might request to refund the transaction. You can directly refund transactions to your customer’s account via OY! Dashboard. A refund can either be full or partial. A full refund gives your customers the entire amount back (100%). A partial refund returns the amount partially. + +Refunds are free of charge. However, the admin fee charged for the original transaction is not refunded by OY! to your balance. + +There are several requirements that must be met to issue a refund: + +1. Refunds can only be issued up to 7 calendar days after the transaction is marked as successful. +1. You have enough balance that allows us to deduct the amount of the transaction that should be refunded. +1. A refund can only be issued once for each successful transaction, whether it is a full or partial refund. +1. Refunds must be issued during operational hours, depending on the payment method. Refer to the table below. + +Currently, refunds are only available for DANA, ShopeePay, and LinkAja. + +|Payment Method|Refund Feature|Operational Hours| +| :- | :- | :- | +|DANA |Full Refund, Partial Refund|00\.00 - 23.59 GMT+7| +|ShopeePay|Full Refund|05\.00 - 23.59 GMT+7| +|LinkAja |Full Refund|00\.00 - 23.59 GMT+7| +|OVO|Not supported|-| + +If you use Refund API, OY! will send a notification to your system via callback once a transaction is successfully refunded [Refund Callback - API Docs](https://api-docs.oyindonesia.com/#callback-parameters-e-wallet-refund-callback). You can also check the status of your refund request via API Refund Check Status. Refund [Check Status - API Docs](https://api-docs.oyindonesia.com/#get-e-wallet-refund-status-api-e-wallet-aggregator) +### Registration and Setup +Here are the steps to guide you through registration and setup for creating E-wallet Aggregator transactions. + +1. Create an account at OY! Dashboard +1. Do account verification by submitting the verification form. Ensure to tick the “Receive Money” product since E-wallet Aggregator is a part of Receive Money products. +1. OY! team will review and verify the form and documents submitted +1. Once verification is approved, set up your receiving bank account information. Important Note: Ensure that the receiving bank account information is accurate as you can only set it up once via OY! Dashboard for security reasons +1. Follow the registration process for each e-wallet that you want to use. Please refer to this section for detailed guidelines: [e-wallet Activation](https://docs.oyindonesia.com/#e-wallet-payment-methods) +1. Submit your IP address(es) & callback URL to your business representative or send an email to our support team, . +1. OY! will send the Production API Key as an API authorization through your business representative. + Note: Staging/Demo API Key can be accessed via OY! Dashboard by going to the “Demo” environment and the key can be found on the bottom left menu. +1. Integrate E-wallet API to your system. Please follow the API documentation to guide you through.[ E-wallet - API Docs](https://api-docs.oyindonesia.com/#create-e-wallet-transaction-api-e-wallet-aggregator) + +### Creating E-wallet transactions +Create E-wallet Transactions: Use this API to create an E-wallet transaction for your user + +You can create E-Wallet transactions via API only. Here are the guidelines to create E-wallet transactions via API: + +1. Integrate API Create E-wallet transactions to your system. [Create E-wallet - API Docs](https://api-docs.oyindonesia.com/#https-request-create-e-wallet-transaction) +1. Hit OY!’s API to Create E-wallet transaction. +1. OY! will return the information to complete the payment + - For e-wallets that use the redirection method (i.e. ShopeePay, DANA, LinkAja), OY! will return the e-wallet URL to complete the payment. You can share the URL to your customer. + - For e-wallets that use the push notification method (i.e. OVO), the e-wallet provider will send a notification to your customer’s e-wallet app to complete the payment + +Note: When you hit Create E-Wallet Transaction endpoint in Staging/Demo environment, it will always return the same ewallet_url & success_redirect URL in the response: https://pay-dev.shareitpay.in/aggregate-pay-gate. You cannot simulate payment by clicking this URL. + +In order to be able to simulate payment, please refer to this section: Simulate [E-Wallet Payments - Product Docs](https://docs.oyindonesia.com/#e-wallet-payment-methods) + +### Completing transaction +Each e-wallet provider has a different method to complete the transaction, redirection or push notification method. ShopeePay, LinkAja, and DANA use a redirection method. Meanwhile, OVO uses a push notification method. Please refer to these guidelines for completing transactions based on each provider: + +1. Complete e-wallet transactions via ShopeePay [ShopeePay Payment Journey](https://docs.oyindonesia.com/#e-wallet-payment-methods) +1. Complete e-wallet transactions via LinkAja [LinkAja Payment Journey](https://docs.oyindonesia.com/#e-wallet-payment-methods) +1. Complete e-wallet transactions via DANA [DANA Payment Journey](https://docs.oyindonesia.com/#e-wallet-payment-methods) +1. Complete e-wallet transactions via OVO [OVO Payment Journey](https://docs.oyindonesia.com/#e-wallet-payment-methods) + +To simulate demo transactions, please refer to this section: Simulate [E-Wallet Payments - Product Docs](https://docs.oyindonesia.com/#e-wallet-payment-methods) + +### Checking transaction status +All created E-wallet transactions are shown in OY! Dashboard. Navigate to “E-Wallet” to see the list of transactions. Inside the dashboard, you can see the details of the transactions, including all the transaction information inputted during creation, status of transactions, and the payment reference number. The dashboard also has a feature to search, filter, and export the list of transactions in various formats: Excel (.xlsx), PDF (.pdf), and CSV(.csv) + +There might be times when your customer already completed the payments but the transaction status is not updated to success. Therefore, we also recommend you to check the transaction status periodically via the Check Status E-wallet API. [Check Status E-Wallet - API Docs](https://api-docs.oyindonesia.com/#https-request-check-e-wallet-transaction-status) + +### Receiving fund to balance +Once a transaction is paid by the customer, OY! updates the transaction status and sends callback to your system that the transaction has been paid. OY! also sends/settles the funds to your OY! balance. Each provider has a different settlement time, varying from D+1 to D+2 working days. + +## API Payment Routing +Payment Routing API is a service that allows you to receive payments & send money in one integrated API. It enables you to automatically send money to several recipients once you receive payments from your customers. You can save development time by integrating with Payment Routing API as it provides two services at once + + + + + + + + + + + + + + + + + + + + + + + + +
Payment Routing TypeFeatures
Transaction TypePayment Aggregator

Receive payments only

All-in-one API to receive payments via bank transfers, e-wallets, QRIS, and cards.

Payment RoutingReceive payments and automatically send the money to several recipients
Receive Money TypeWithout UI +

You have your own checkout page and OY! provides the payment details

+

+

OY! provides the payment details information after creation (e.g. VA number, e-wallet URL, QR code URL, etc)

+

+

Support one payment method only (Single Payment & Direct Payment)

+
With UI +

Use OY! built-in checkout page (Payment Link)

+

+

OY! provides the Payment Link after creation

+

+

Support multiple payment methods in one transaction

+
+ +#### Use Cases +**Payment Aggregator** + +1. Single Payments + - Single payment is a type of payment that allows your customer to complete payments easily. Available for Bank Transfer (Virtual Account & Unique Code), E-Wallet, QRIS, and Cards. +1. Direct Payments + - Direct payment requires account linking, meaning that your customer must connect their payment account to your system before completing payments. You can do this by using our API Account Linking. Direct payments offer a more seamless payment experience. After successful linkage, your customer does not need to open or get redirected to the payment provider application to complete payments. This feature is currently only available for e-wallet ShopeePay. + +**Payment Routing** + +1. Investment + - OJK regulation does not allow investment applications to keep users balance. Use payment routing to receive funds from investors and directly send the funds to custodian banks. +2. E-Commerce + - Receive goods payments from customers and directly send the merchant’s share to the merchant’s bank account. +3. Education + - Receive tuition payments from parents and directly send the admin fee to the school’s bank account +4. Loan Application + - Receive loan repayments from the borrower and directly disburse the money to the lender’s pooling account or the borrower’s pooling account. + + +### Flow + +![Payment Routing Aggregator Scheme](images/acceptingPayments/api-payment-routing/payment-flow-without-ui-scheme.webp) + +![Payment Routing Payment Link Scheme](images/acceptingPayments/api-payment-routing/payment-flow-with-ui-scheme.webp) + +### Features + +#### Support multiple payment methods +OY! supports various payment methods in the Payment Routing API, including: + +1. Bank Transfer + - Virtual Account: BCA, BNI, BRI, Mandiri, CIMB, SMBC, BSI, Permata + - Unique Code: BCA +1. E-wallet + - Single Payments: ShopeePay, DANA, LinkAja + - Direct Payments: ShopeePay +1. Cards: Visa, Mastercard, JCB +1. QRIS + +#### Send money to multiple recipients in a real-time manner +Once you receive payments from the customer, OY! can directly send the money up to 10 recipients without waiting for the settlement, as long as you have enough balance in your OY! account. Depending on the payment methods used, some transactions might not be settled real-time to your balance (e.g. QRIS, e-wallet). Therefore, you must keep enough balance to cater the sending money process. + +#### Use Payment Links to receive money +There are two types of receiving money: With UI and Without UI. The Without UI scheme can be used if you have your own checkout page and you only need the payment details information to complete the payments. Here are the payment information you will receive after successful transaction creation: + +1. Bank Transfer - Virtual Account: destination bank, VA number, and amount of transaction +1. Bank Transfer - Unique Code: destination bank, bank account number, bank account name, billed amount (original amount), unique amount, and total amount (final amount) +1. QRIS: URL to access the QR code +1. E-wallets: link to redirect your customer to the respective e-wallet selected +1. Cards: link to redirect your customer to fill in card details and proceed to payment + +However, if you do not have your own checkout page, no need to worry as you can use OY’s checkout page (Payment Link) for Payment Routing transactions. You can do so by filling the “need\_frontend” parameter with “TRUE” in the creation API. + +Read more about payment link in [Payment Link - Product Docs](https://docs.oyindonesia.com/#payment-link-accepting-payments). + + +#### Create E-Wallet Direct Payment transactions +Payment Routing API supports Direct Payment transactions where your customer is not redirected to an external payment provider’s application/website to complete payments, resulting in a more seamless transaction and better payment experience. This feature is currently only supported for e-Wallet ShopeePay + +Refer to this section to understand how e-Wallet One-Time payments differs from Direct Payments: [E-Wallet Payment Type](https://docs.oyindonesia.com/#e-wallet-payment-methods) + +#### Transaction tracking and monitoring capability +All created Payment Routing transactions are shown in the OY! Dashboard. Navigate to “Payment Routing” to see the list of created transactions. Inside the dashboard, you can see the details of the transactions, including all the transaction information inputted during creation, the transaction status , and the payment reference number\*. The dashboard also has a feature to search, filter, and export the list of transactions in various formats: Excel (.xlsx), PDF (.pdf), and CSV (.csv) + +![Payment Routing Monitoring Transactions](images/acceptingPayments/api-payment-routing/dashboard.webp) + +\*Payment Reference Number is an identifier of a payment attempt when the customer successfully completes a QRIS payment. The reference number is also displayed in the customer’s receipt/proof of transaction. Only available for QRIS transactions. +#### Use the same Virtual Account number for different transactions +One customer might do payments for multiple transactions and use the same bank each time. By generating the same VA number for different transactions, it makes the payment easier for your customer as they can save the VA number on their mobile banking application. You can only use the same VA number for one active transaction at a time. +### Registration and Setup +Here are the steps to guide you through registration and set up for creating Payment Routing transactions. + +1. Create an account at OY! Dashboard +1. Do account verification by submitting the verification form. Ensure to tick the “Receive Money” and “Send Money” products since Payment Routing is a part of Receive Money & Send Money products. +1. OY! team will review and verify the form and documents submitted +1. Once verification is approved, set up your receiving bank account information. Important Note: Ensure that the receiving bank account information is accurate as you can only set it up once via OY! Dashboard for security reasons +1. By default, you get several payment methods on the get go, including all Bank Transfers (excl. BCA) +1. Other payment methods like QRIS, E-wallets, Cards, and BCA need additional onboarding to be available to use. Please refer to this section for detail guidelines: + 1. [E-wallet Onboarding](https://docs.oyindonesia.com/#e-wallet-payment-methods) + 1. [QRIS Onboarding](https://docs.oyindonesia.com/#qris-payment-methods) + 1. [VA BCA Onboarding](https://docs.oyindonesia.com/#bank-transfer-virtual-account-payment-methods) + 1. [Cards Onboarding](https://docs.oyindonesia.com/#cards-payment-methods-payment-methods) +1. Submit your IP address(es) & callback URL to your business representative or send an email to +1. OY! will send the Production API Key as an API authorization through your business representative. + Note: Staging/Demo API Key can be accessed via OY! Dashboard by going to the “Demo” environment and the key can be found on the bottom left menu. +1. Integrate Payment Routing API to your system. Please follow the API documentation to guide you through. [Payment Routing - API Docs](https://api-docs.oyindonesia.com/#payment-routing) + +### Creating Payment Routing transactions +Once you successfully complete the registration process, you can immediately create Payment Routing transactions (via API only). You can create a transaction using the Without UI scheme or With UI scheme, depending on the use case. + +#### Without UI Scheme +1. Integrate API Create Payment Routing transactions to your system. [Create Payment Routing - API Docs](https://api-docs.oyindonesia.com/#https-request-create-and-update-payment-routing) +1. Hit OY!’s API Create Payment Routing transaction + 1. Insert parameter “need\_frontend” with "FALSE" + 1. Choose one payment method to accept the payment. You can choose between BANK\_TRANSFER, EWALLET, QRIS, or CARDS. + 1. Insert the chosen payment method into the “list\_enable\_payment\_method” parameter. Note: Ensure that you only input one payment method since you use the Without UI scheme, otherwise you will get an error message + 1. Choose one bank/payment provider (SOF) based on the payment method. + 1. BANK\_TRANSFER: 014, 009, 002, 008, 022, 213, 011, 016, 484, 451, 013. Refer to this section to see all supported banks and the bank\_code: Bank Transfer - Payment Method + 1. EWALLET: shopeepay\_ewallet, dana\_ewallet, linkaja\_ewallet + 1. QRIS: QRIS + 1. CARDS: CARDS + 1. Insert the chosen SOF into the “list\_enable\_sof” parameter. Note: Ensure that you only input one SOF since you use the Without UI scheme, otherwise you will get an error message + 1. If you want to use e-wallet Direct Payment, fill “use\_linked\_account” with “TRUE”, otherwise fill the parameter with “FALSE”. Only available for ShopeePay. You must do Account Linking prior to creating direct payment transactions. Refer to this section to understand about [Account Linking](https://docs.oyindonesia.com/#api-account-linking-accepting-payments). + 1. If you want to send the money once you receive payments, fill in the destination bank account number and the amount of each recipient under the “payment\_routing” object. +1. OY! will return the information to complete the payment based on the requested payment method + 1. Bank Transfer - Virtual Account: destination bank, VA number, and amount of transaction + 1. Bank Transfer - Unique Code: destination bank, bank account number, bank account name, billed amount (original amount), unique amount, and total amount (final amount) + 1. QRIS: URL to access the QR code + 1. E-wallets: link to redirect your customer to the respective e-wallet selected + 1. Cards: link to redirect your customer to fill in card details and proceed to payment +1. Show the payment details to your customer inside your application + +#### With UI Scheme +1. Integrate API Create Payment Routing transactions to your system. [Create Payment Routing - API Docs](https://api-docs.oyindonesia.com/#https-request-create-and-update-payment-routing) +1. Hit OY!’s API Create Payment Routing transaction + 1. Insert parameter “need\_frontend” with "TRUE" + 1. Choose the list of payment methods to accept the payment. You can choose to insert BANK\_TRANSFER, EWALLET, QRIS, and CARDS. + 1. Insert the list of chosen payment methods into the “list\_enable\_payment\_method” parameter. You can insert multiple payment methods to let your customer choose the preferred payment method + 1. Choose banks/payment providers (SOF) for each payment method. + 1. BANK\_TRANSFER: 014, 009, 002, 008, 022, 213, 011, 016, 484, 451, 013 + 1. EWALLET: shopeepay\_ewallet, dana\_ewallet, linkaja\_ewallet + 1. QRIS: QRIS + 1. CARDS: CARDS + 1. Insert the list of chosen SOFs into the “list\_enable\_sof” parameter. You can insert multiple banks/payment providers to let your customer choose the preferred payment method + 1. If you want to send the money once you receive payments, fill in the destination bank account number and the amount of each recipient under the “payment\_routing” object. +1. OY! will return the Payment Link URL and you can share this to your customer. + +### Completing payments + +#### Without UI Scheme +Each payment method has a different flow to complete the transaction, depending on the nature of each payment method. Please refer to these guidelines for completing transactions based on each payment method: + +1. [Complete Bank Transfer - Virtual Account transactions](https://docs.oyindonesia.com/#bank-transfer-virtual-account-payment-methods) +1. [Complete Bank Transfer - Unique Code transactions](https://docs.oyindonesia.com/#bank-transfer-unique-code-payment-methods) +1. [Complete QRIS transactions](https://docs.oyindonesia.com/#qris-payment-methods) +1. [Complete E-wallet transactions](https://docs.oyindonesia.com/#e-wallet-payment-methods) +1. [Complete Cards transactions](https://docs.oyindonesia.com/#cards-payment-methods-payment-methods) + +To simulate demo/staging transactions, please refer to this section: + +1. [Simulate Bank Transfer - Virtual Account payments](https://docs.oyindonesia.com/#bank-transfer-virtual-account-payment-methods) +1. [Simulate Bank Transfer - Unique Code payments](https://docs.oyindonesia.com/#bank-transfer-unique-code-payment-methods) +1. Simulate QRIS payments\* +1. [Simulate E-wallet payments](https://docs.oyindonesia.com/#e-wallet-payment-methods) +1. [Simulate Cards payments](https://docs.oyindonesia.com/#cards-payment-methods-payment-methods) + +\*currently not available + +#### With UI Scheme +Once you successfully create a Payment Routing using With UI scheme, you may share the link to your customers. The steps for your customers to complete a transaction using With UI scheme is the same as completing a Payment Link transaction. Please refer to this section: +[Completing Payment Link transactions - Product Docs](https://docs.oyindonesia.com/#completing-payments-payment-link) +### Checking transaction status +All created Payment Routing transactions are shown in OY! Dashboard. Navigate to “Payment Routing” to see the list of created transactions. Inside the dashboard, you can see the details of the transactions, including all the transaction information inputted during creation, transaction status, and the payment reference number\*. The dashboard also has a feature to search, filter, and export the list of transactions in various formats: Excel (.xlsx), PDF (.pdf), and CSV (.csv) + +If for some reason you do not receive our transaction callbacks successfully, you may use our API Check Status to get the latest transaction status. [Check Status Payment Routing - API Docs](https://api-docs.oyindonesia.com/#check-status-payment-routing-transaction-payment-routing). +## +\*Payment Reference Number is an identifier of a payment attempt when the customer successfully completes a QRIS payment. The reference number is also displayed in the customer’s receipt/proof of transaction. Only available for QRIS transactions. +### Receiving fund to balance +Once a transaction is paid by the customer, OY! updates the transaction status and sends notification to your system indicating that the transaction has been paid, and settles the funds to your OY! balance. Each payment method has a different settlement time, varying from real-time to D+2 working days. +### Sending funds to recipients +Payment Routing allows you to disburse funds automatically once the transaction is paid by the customer. OY! automatically sends the funds to the recipient(s) stated in the creation process once the payment is received. You need to make sure that you have enough balance to carry out the disbursement process, especially for payment methods that have non-real time settlement; otherwise, the disbursement process fails due to insufficient balance. + +## QRIS Aggregator +Quick Response Code Indonesian Standard (QRIS) is a standardized QR payments in Indonesia that are developed by Bank Indonesia. Payments are performed by the customers scanning the QR on their m-banking/e-wallet application. QR payments are highly suitable for low-value transactions since they offer an affordable price (0.7% per transaction). QRIS aggregator allows you to create QRIS transactions as a payment method to be displayed to your customer. + +**Note**: QRIS Aggregator is implemented using existing Payment Routing API. This section highlights only the features, flow, etc. that relevant for QRIS Aggregator transaction. All transactions will be displayed in OY! Dashboard under Payment Routing menu. + +### Flow +![QRIS Aggregator Scheme](images/acceptingPayments/qris-aggregator/qris-flow.webp) + +### Features +The maximum amount per transaction for QRIS is Rp 10,000,000. The minimum amount per transaction is Rp 10,000. Should you have any request to receive payments below Rp 10,000, please contact your Business Representative. + +### Registration and Setup +Here are the steps to guide you through registration and set up for creating QRIS Aggregator transactions. + +1. Create an account at OY! Dashboard +1. Do account verification by submitting the verification form. Ensure to tick the “Receive Money” and “Send Money” products since Payment Routing is a part of Receive Money & Send Money products. +1. OY! team will review and verify the form and documents submitted +1. Once verification is approved, set up your receiving bank account information. + Important Note: Ensure that the receiving bank account information is accurate as you can only set it up once via OY! Dashboard for security reasons +1. Refer to this section for detailed guidelines on [how to activate QRIS Payment Method](https://docs.oyindonesia.com/#qr-code-qris-payment-methods) +1. Submit your IP address(es) & callback URL to your business representative or send an email to business.support@oyindonesia.com +1. OY! will send the Production API Key as an API authorization through your business representative. + Note: Staging/Demo API Key can be accessed via OY! Dashboard by going to the “Demo” environment and the key can be found on the bottom left menu. +1. Integrate QRIS Aggregator via Payment Routing API to your system. Please follow the API documentation to guide you through [QRIS Aggregator - API Docs](https://api-docs.oyindonesia.com/#qris-aggregator). + +### Creating QRIS Aggregator Transaction +Once you successfully complete the registration process for QRIS payment method, you can immediately create QRIS Aggregator transaction via Payment Routing (via API only). You will use Payment Routing Without UI when implementing QRIS Aggregator scheme. + +1. Integrate [API QRIS Aggregator](https://api-docs.oyindonesia.com/#qris-aggregator) via Payment Routing. +1. Hit OY!’s [API Create QRIS transaction](https://api-docs.oyindonesia.com/#create-qris-transaction-qris-aggregator) + 1. Insert parameter “need_frontend” with "false" + 1. Choose QRIS as Payment Method by + 1. Filling QRIS in list_enable_payment_method parameter + 1. Filling QRIS in list_enable_sof +1. OY! will return url to access the QR code to complete the payment +1. Show the QR code to your customer inside your application + +### Completing Payment +Please refer to [this section](https://docs.oyindonesia.com/#qr-code-qris-payment-methods). + +### Checking Transaction Status +All created QRIS Aggregator transactions are shown in OY! Dashboard. Navigate to “Payment Routing” to see the list of created transactions. Inside the dashboard, you can see the details of the transactions, including all the transaction information inputted during creation, transaction status, and the payment reference number*. The dashboard also has a feature to search, filter, and export the list of transactions in various formats: Excel (.xlsx), PDF (.pdf), and CSV (.csv) + +If for some reason you do not receive our transaction callbacks successfully, you may use our API Check Status to get the latest transaction status. [Check Status QRIS Transaction - API Docs](https://api-docs.oyindonesia.com/#check-status-qris-transaction-qris-aggregator). + +*Payment Reference Number is an identifier of a payment attempt when the customer successfully completes a QRIS payment. The reference number is also displayed in the customer’s receipt/proof of transaction. + +### Receiving Fund to Balance +Once a transaction is paid by the customer, OY! updates the transaction status and sends notification to your system indicating that the transaction has been paid, and settles the funds to your OY! balance. Each payment method has a different settlement time, varying from real-time to D+2 working days. + +## API Account Linking +Account Linking is a feature that allows your customer's payment account to be linked to your system using tokenization. By linking the customer’s account upfront, your customer can see their account balance inside your application and later on can complete payments without being prompted for any card details or e-wallet phone number. The feature is currently supported for e-wallet ShopeePay & DANA. + +Account linking feature is free of charge. + +### Flow + +![Account Linking Flow](images/acceptingPayments/api-account-linking/payment-flow-api-account-linking.webp) +![Get Account Balance Flow](images/acceptingPayments/api-account-linking/payment-flow-api-get-e-wallet-balance.webp) +![Account Unlinking API Flow](images/acceptingPayments/api-account-linking/payment-flow-unlink-account-via-api-accoung-linking.webp) +![Account Unlinking via App Flow](images/acceptingPayments/api-account-linking/payment-flow-unlink-account-via-e-wallet-app.webp) + +### Registration and Setup +Here are the steps to guide you through registration and set up for doing Account Linking. + +1. Create an account at OY! Dashboard +1. Do account verification by submitting the verification form. Ensure to tick the “Receive Money” since Account Linking is a part of Receive Money. +1. OY! team will review and verify the form and documents submitted. +1. Once verification is approved, set up your receiving bank account information. + - Important Note: Ensure that the receiving bank account information is accurate as you can only set it up once via OY! Dashboard for security reasons +1. Submit your IP address(es), callback URL, and redirect URL to your business representative or send an email to +1. OY! will send the Production API Key as an API authorization through your business representative. + Note: Staging/Demo API Key can be accessed via OY! Dashboard by going to the “Demo” environment and the key can be found on the bottom left menu. +1. Integrate Account Linking API to your system. Please follow the API documentation to guide you through.[Account Linking - API Docs](https://api-docs.oyindonesia.com/#api-account-linking) + +### Link customer’s payment account to your application +Customers can link their payment account to your application by hitting the API Account Linking. Here are the steps to guide you and your customer when doing account linking: + +1. Integrate API Account Linking to your system. [Account Linking - API Docs](https://api-docs.oyindonesia.com/#get-linking-url-api-account-linking) +1. Hit OY!’s [API Get Linking URL](https://api-docs.oyindonesia.com/#get-linking-url-api-account-linking) . You will receive a linking URL as a response. The linking URL is used for the customer to authorize the linking request by giving a permission. +1. Customer gives a permission and inputs PIN to authorize the request +1. Payment provider will process the request and OY! will send you a callback to notify that the account is successfully linked +1. Customer is redirected to the redirect URL you specified when hitting the Get Linking URL API  + +### Check customer’s payment account balance +Once the customer linked their payment account, you can get the information of the customer's account balance by hitting the API Get E-wallet balance. You can show the balance inside your application. For instance, show the balance during the checkout process so the customer can know their balance before choosing a payment method. Please refer to the API Docs for more details: [Get Account Balance API - API Docs](https://api-docs.oyindonesia.com/#get-e-wallet-account-balance-api-account-linking) + +### Unlink customer’s payment account from your application +Customers who have linked their payment account can unlink their account anytime. They can do so via API Account Unlinking or via Payment Provider Application. Using the API Account Unlinking allows your customers to unlink their account inside your application. Another option that the customer can do is to unlink their account via the payment provider’s application. + + +Here are the steps to guide you and your customer when unlinking an account: + +**API Account Unlinking** + +1. Integrate API Account Unlinking to your system. [Account Unlinking - API Docs](https://api-docs.oyindonesia.com/#unlink-account-api-account-linking) +1. Hit OY!’s API Account Unlinking. Once you hit our API, OY! will hit the provider’s system to unlink the customer’s account +1. OY! will send a callback to let you know that the unlinking is successful + + +**Payment Provider Application** + +ShopeePay + +1. Open Shopee app +1. Navigate to Setting → Apps Linked to ShopeePay +1. Click unlink account for your merchant + +DANA + +1. Open DANA app +1. Navigate to Account → Linked Accounts +1. Click remove linking for your merchant +1. If your customer's DANA account is frozen, then their account is temporarily unlinked. Once the account is unfrozen and the token has not expired, their account is automatically linked again. + + +## Account Receivable + +Payment Link +Account Receivable product provides features to help you manage your invoices and payments. This product supports invoice customization and payment customization based on your customer needs. Creation for Account Receivable is available on OY! dashboard and/or API. + +**Disclaimer:** the word “account receivable” is used interchangeably with “invoice” in this document + +**Payment Flow** + +1. You create account receivable (i.e. invoice) for your customers and share it through email or WhatsApp +1. Your customers can make payments either through the link attached to the invoice or directly via a Virtual Account (VA) number. +1. OY! detects the payments and notifies you about the payments through callback and payment status update on your dashboard +1. The payments received will be settled in your OY! dashboard + + +### Key Features + +#### Various options of creating account receivable + +**1. Creating account receivable through dashboard** + + +* **No integration needed** + +Offer easiest way to invoicing through dashboard without any technical development. + + +* **Manage your customer data easily** + +You can edit or deactivate the customer data as you need. The customization includes taxes imposed and type of payment as explained below (with payment invoice link or fixed virtual account), so it can be customizable according to your needs. + + +* **Various payment methods** + +We have various payment options that you can use to receive payments from your customer: + +1. Payment invoice link (Bank Transfer (via Virtual Account and Unique Code), Credit/Debit card, E-Wallet (ShopeePay, DANA, LinkAja, OVO), QRIS, and Retail outlets (Alfamart and Indomaret)) +1. Fixed Virtual Account. Fixed VA is a virtual account where your customer uses the same account number for every payment. This means they will pay the same virtual account for every invoice you send. However, you can’t send a new invoice to the customer until the previous one has been paid. + + +* **Customizable Invoice to Personalize Your Business** + +Create invoice templates based on your business personalization. We provide a lot of invoice templates and are able to change the color and business logo that suits your business branding. + + +**2. Creating account receivable through API** + +* Seamless integration with your customer's purchase journey. Simply send us an API request and we will respond with an account receivable (i.e. invoice) link that you can embed to your system. + +* Added level of customization +Below are the things that you can customize: + +1. Amount (specify the amount and choose between open amount vs closed amount) +1. Admin fee (choose whether the admin fee will be paid by your customers or borne by you) +1. Payment method (choose the payment methods displayed to your customers among Bank Transfer (via Virtual Account and Unique Code), Credit/Debit card, E-Wallet (ShopeePay, DANA, LinkAja, OVO), QRIS, and retail outlets (Alfamart and Indomaret). Additionally, you can choose which banks are enabled for Bank Transfer method. +1. Payment invoice expiration date +1. The customer data for invoicing + +* **Various Payment Methods.** Our payment invoice provides multiple payment options: Bank Transfer (via Virtual Account and Unique Code), Credit/Debit card, E-Wallet (ShopeePay, DANA, LinkAja, OVO), QRIS, and retail outlets (Alfamart and Indomaret). + +* **Upload or Create a PDF for your Invoice Billing.** We help you to generate your invoice using OY! PDF templates also you can attach your invoice supporting documents via our API. + +* **Account Receivable Delivery by Email and/or WhatsApp.** You can choose to send the created link to your customers through Email and/or WhatsApp for better payment conversion. By default, our system will send the invoice through email but if you want to share the invoice and payment invoice link through WhatsApp, follow the steps [here](https://api-docs.oyindonesia.com/#resend-invoice-api-account-receivable). + + +#### Capability to monitor payment invoice/account receivable details on dashboard + +Whether you create the account receivable through dashboard or API, you can check the list of transactions through dashboard easily. + +#### Support Multi Entity Management + +With this feature, you will be able to create invoices from your users through account receivable created on behalf of a Sub-Entity account. When your customers make a successful transaction, the transaction will be recorded in the Sub-Entity Account's balance. As a main account, you can view the Sub-Entity Account's balance and transaction list anytime under “Sub-Entity Statement” in the “Multi Entity” menu. + +Click [here](https://docs.oyindonesia.com/#multi-entity-management-oy-dashboard-tutorial) for more information on this feature. + + +### Registration and Set Up + +#### For dashboard-generated invoices + +Follow this check-list to ensure you're all set up to use the service: + +1. Create an account for OY! business +1. Upgrade your account by submitting the required documentation +1. Have your upgrade request approved +1. Set up your receiving bank account information (note: ensure that the receiving bank account information is accurate as it cannot be changed via OY! dashboard for security reasons) +1. Once your account is approved, you can start creating account receivable transactions + + +#### For API-generated invoices + +1. Create an account OY! business +1. Upgrade your account by submitting the required documentation +1. Have your upgrade request approved +1. Set up your receiving bank account information (note: ensure that the receiving bank account information is accurate as it cannot be changed via OY! dashboard for security reasons) +1. Submit your IPs to your business representative +1. Set your callback URLs in “Developer Option” under the “Settings” menu. Please input your callback URLs in the Payment Link’s section +1. Receive an API Key from us (note: it is required for API authorization purpose) +1. Integrate with our [Account Receivable API](https://api-docs.oyindonesia.com/#create-api-account-receivable) + + +### Testing + +#### Creating dashboard-generated dummy account receivable + +1. Log on to your OY! dashboard +1. Choose "Demo" environment +1. Click “Customer Data” under Receive Money menu +1. Click “Create New Customer” on the top right corner +1. Fill in the required fields regarding Customer Information then click “Next” +1. Fill in the required fields regarding Tax and Payment Information then click “Save” +1. Start to create a new invoice by choosing “Account Receivable” under the Receive Money menu +1. Click "Create New Invoice" on the top right corner +1. Fill in the necessary details + +| Parameter | Description | +|------|------| +| Invoice Number| The number of the invoice to be created | +| Invoice Date | The date of the invoice | +| Due Date | Due date of a transaction. You can choose between 7, 14, 30, 45, or 60 days after the created date of the invoice OR you can also input a specific/custom date. Your customer will get reminders to pay on D-1, D-Day, and D+7 from the transaction due date through email. | +| Link Expiry Datetime | You can set your payment invoice link expiry date and time for your convenience. The expiry time selected will also appear on PDF documents. | +| Customer | The name of the customer whom the invoice is addressed to. You can choose the name of the customer from the dropdown. To create a new customer, follow the instructions [here.](https://docs.oyindonesia.com/#creating-a-customer-for-account-receivable-invoice-payment-links-invoice)| +| Product Description | The name and/or description of the product | +| Quantity | The quantity of the product | +| Unit Price | Unit price of the product | +| Amount | Total amount for the product (amount = quantity x unit price) | +| Notes | The note to be displayed in the automatically generated invoice file | +| Additional Documents | The supporting documents that will be attached in the email along with the invoice. Accept PDF & Excel files. Maximum of 4 documents (maximum 5MB each). | +| Invoice Payment | You can choose between "Payment Link" (the invoice will be embedded with a payment link that the customer can use to make a payment) or "Invoice Only" (the invoice will not be embedded with a payment link). For "Invoice Only", invoice status can be adjusted at any time for record purposes. | +| Payment Method | The payment method that you can choose to enable/disable for your customers only if you choose “Payment Link” as your invoice payment type above. The payment methods available are Bank Transfer (via Virtual Account and Unique Code), Credit/Debit card, E-Wallet (ShopeePay, DANA, LinkAja, OVO), QRIS, and retail outlets (Alfamart and Indomaret). | +| Admin Fee Method | You can choose between "Included in total amount" or "Excluded from total amount". "Included in total amount" means the admin fee will be deducted from the payment amount made by the customer. "Excluded from total amount" means the admin fee will be added to the customer's total payment (Total Amount = Specified Amount + Admin Fee) | + + +#### Creating API-generated dummy account receivable + +1. Create an OY! business account +1. Send a request to activate API Payment Link product and obtain staging API Key to your business representative +1. Create the customer data first by sending a request to https://api-stg.oyindonesia.com/api/account-receivable/customers. Enter the required and optional fields, as referenced in the [API reference docs](https://api-docs.oyindonesia.com/#https-request-create) +1. Then, you can try to create an account receivable by sending a request to https://api-stg.oyindonesia.com/api/account-receivable/invoices. Enter the required and optional fields, as referenced in the [API reference docs](https://api-docs.oyindonesia.com/#create-amp-send-invoice-api-account-receivable) + + +#### Accessing and monitoring the created test account receivable + +Whether you create the link through dashboard or API, you can see the details of your link on the OY! Dashboard, you can check it on “Account Receivable” under the Receive Money menu. + + +#### Mock Credentials for Testing + +* For payment via Credit Card or Debit Card, you may use the credentials below to simulate an end-to-end payment journey for a successful transaction in the staging environment: + +| Card Details | Values | +|--------|-------| +| Card Number | 2223000000000007 | +| Card Expired Month/Year | 01/39 | +| Card CVN | 100 | +| Card Holder Name | John Doe | + + +1. Click the payment invoice link +1. You’ll be redirected to page to choose which transaction you want to proceed +1. Choose your payment method (in this case please choose “Credit/Debit Card”) then click “Bayar” +1. You’ll be redirected to the summary of the payment +1. Please click “Bayar via CARDS” +1. You’ll be redirected to page to process your payment +1. Please fill in the credentials above, email, phone number, and also mark the box for terms and conditions then click “Pay” +1. You’ll see loading page that informs you the system is processing your payment +1. Congratulations! You’ve completed your (dummy) payment on account receivable using credit/debit card + + +* For bank transfer you can test process payment from payment invoice link and mock the transaction using callback from our OY! Dashboard with demo environment + +1. Click the payment invoice link +1. You’ll be redirected to page to choose which transaction you want to proceed +1. Choose your payment method (in this case please choose bank transfer BCA) then click “Bayar” +1. You’ll see loading page that informs you the system is processing your payment +1. Copy the VA number then open the OY! dashboard and choose demo environment +1. Go to section “Callback Bank Transfer” under the “Settings” menu +1. Select the Transaction Type (in this case please select “Virtual Account”), Bank Name, VA Number, Amount, and Payment Date and Time then click “Send Callback” +1. Congratulations! You’ve completed your (dummy) payment on account receivable using bank transfer + + +* For e-wallet you can test to process payment from payment invoice link, specifically Shopeepay and LinkAja + +1. Click the payment invoice link +1. You’ll be redirected to page to choose which transaction you want to proceed +1. Choose your payment method (in this case please choose e-wallet category) then click “Bayar” +1. You’ll see loading page that informs you the system is processing your payment +1. Go to “One Time” on section “Payment Link” +1. Copy the “Ref Number” on transaction you’ve created for Account Receivable (you can see the amount of transaction and customer name) +1. Go to the “E-wallet Callback” +1. Choose e-wallet type you’ve choose on the payment link +1. Paste the “Ref Number” on the form Ref Number +1. Put the amount of transaction +1. Click “Send Callback”, you can check your transaction successfully paid on Account Receivable (demo) page +1. Congratulations! You’ve completed your (dummy) payment on account receivable using e-wallet + + +**Note:** Currently we only provide dummy transactions using credit/debit card, bank transfer, and e-wallet. Please use only the mentioned payment method, otherwise your payment may not be processed successfully. + + +### How to Use Account Receivable via Dashboard + +1. Log on to your OY! dashboard +1. Choose "Production" environment +1. Choose “Account Receivable” under Receive Money menu +1. Click "Create New Invoice" on the top right corner +1. Fill in the necessary details + +| Parameter | Description | +|------|------| +| Invoice Number | The number of the invoice to be created | +| Invoice Date | The date of the invoice | +| Due Date | Due date of a transaction. You can choose between 7, 14, 30, 45, or 60 days after the created date of the invoice OR you can also input a specific/custom date. Your customer will get reminders to pay on D-1, D-Day, and D+7 from the transaction due date through email. | +| Link Expiry Datetime | You can set your payment invoice link expiry date and time for your convenience. The expiry time selected will also appear on PDF documents. | +| Customer | The name of the customer whom the invoice is addressed to. You can choose the name of the customer from the dropdown. To create a new customer, follow the instructions [here](https://docs.oyindonesia.com/#creating-a-customer-for-account-receivable-invoice-payment-links-invoice). | +| Product Description | The name and/or description of the product | +| Quantity | The quantity of the product | +| Unit Price | Unit price of the product | +| Amount | Total amount for the product (amount = quantity x unit price) | +| Notes | The note to be displayed in the automatically generated invoice file | +| Additional Documents | The supporting documents that will be attached in the email along with the invoice. Accept PDF & Excel files. Maximum of 4 documents (maximum 5MB each). | +| Invoice Payment | You can choose between "Payment Link" (the invoice will be embedded with a payment link that the customer can use to make a payment) or "Invoice Only" (the invoice will not be embedded with a payment link). For "Invoice Only", invoice status can be adjusted at any time for record purposes. | +| Payment Method | The payment method that you can choose to enable/disable for your customers. The payment methods available are Bank Transfer (via Virtual Account and Unique Code), Cards (Credit Card/Debit Card), E-Wallet (ShopeePay, DANA, LinkAja, OVO), QRIS, and retail outlets (Alfamart and Indomaret). | +| Admin Fee Method | You can choose between "Included in total amount" or "Excluded from total amount". "Included in total amount" means the admin fee will be deducted from the payment amount made by the customer. "Excluded from total amount" means the admin fee will be added to the customer's total payment (Total Amount = Specified Amount + Admin Fee) | + + +* Create Invoice form +![invoice_creation](images/accountReceivable/invoice_creation_page.png) + + +* Invoice details inside dashboard +![detail_inv_data](images/accountReceivable/detail_invoice_data.png) + + +* Invoice preview inside the dashboard +![prev_inv](images/accountReceivable/invoice_preview.png) + + +### Monitoring the account receivable + +All of the created invoices (via API or Dashboard) can be monitored through your dashboard (Invoice List). + +![ar_table_data](images/accountReceivable/ar_table_data.png) + +![ar_detail_data](images/accountReceivable/ar_detail_data_sidemodal.png) + + +The transaction details that you can see are: + +| Column Name | Definition | +|------|------| +| Invoice Number | The number of the invoice created | +| Customer Name | The name of the customer whom the invoice belongs to the amount billed for that particular transaction | +| Amount Billed | The amount billed for that particular transaction | +| Admin Fee | The admin fee charged for that particular transaction | +| Amount Received | The amount received / the amount of payment made by the customer. This will only be filled in after the customer has completed the payment | +| Invoice Date | The date of the invoice | +| Payment Date | The date of payment (if the invoice has been successfully paid by the customer) | +| Due Date | The invoice due date | +| Days Past Due | How many days an invoice has gone unpaid past the due date. For example, if the due date is 1 July and the invoice is not paid by 4 July, then Days Past Due will be filled in with “Late Payment 3 days”. | +| Payment Link Expiry | Maximum date and time that a payment link can stay valid for before expiring permanently. | +| Status | The transaction status. Possible values are CREATED, PAID, CANCELED, and OVERDUE | + + +In terms of status, below are the status mapping between API Invoice and status in dashboard + +| API Invoice Status | Dashboard Status | +|------|------| +| CREATED, WAITING PAYMENT | UNPAID | +| PAID | PAID | +| CANCELLED | CANCELED | +| OVERDUE | UNPAID (with details under the Late Payment Tab on the Invoice Details page) | + + +Definition for each status + +| API Invoice Status | Explanation | +|------|------| +| CREATED | You already created the account receivable invoice and the customer hasn't made any action. | +| WAITING PAYMENT | Your customer already chooses the payment method and needs to pay before the time limit ends. | +| PAID | Your customer has successfully paid the invoice. | +| CANCELLED | The invoice has already been deleted (this can be from OY! dashboard or API request). | +| OVERDUE | The invoice has passed the invoice payment deadline. | + + +There are several actions that you can take for the created invoice: + +| Action | Definition | +|------|------| +| Send invoice | Send the invoice to the customer's defined email | +| Download invoice | Download the PDF file of the invoice | +| Delete | Delete the invoice. Only invoice with status CREATED can be deleted | + +### Creating a Customer for Account Receivable + +**There are 2 ways to create a Customer:** + +Option 1: through “Create Invoice” +1. Click “Select Customer” +1. Click "Add New Customer" +1. Fill in Customer ID, Customer Name (mandatory), PIC Name, Customer Phone Number, Customer Email, Address, and Tax Type (mandatory). +1. Click “Save” + +* Create Invoice page +![add_cust_creation](images/accountReceivable/add_cust_ar_creation.png) + +* Add Customer from Create Invoice +![add_cust_1](images/accountReceivable/add_cust_ar_creation_step1.png) +![add_cust_2](images/accountReceivable/add_cust_ar_creation_step2.png) + +For tax type, explanation is as follows: + +| Tax Type | Definition | +|------|------| +| No tax | Tax will not be added to the subtotal | +| PPN 10% Inclusive | Tax will not be added upon the subtotal because the subtotal is assumed to be tax inclusive. For transactions prior to April 2022, a 10% PPN applies. | +| PPN 10% Exclusive | Tax will be added separately to the subtotal. For transactions prior to April 2022, a 10% PPN applies. | +| PPN 11% Inclusive |Tax will not be added upon the subtotal because the subtotal is assumed to be tax inclusive. PPN 11% is applicable for transactions after April 2022. | +| PPN 11% Exclusive | Tax will be added separately to the subtotal. PPN 11% is applicable for transactions after April 2022. | +| PPh 23 Non NPWP 4% | Tax will be subtracted from the subtotal | +| PPh 23 NPWP 2% | Tax will be subtracted from the subtotal | + + +Option 2: Through "Customer Management" menu +1. Click "Customer Management" sidebar under the "Receive Money" menu +1. Click "Add new customer" +1. Fill in Customer ID, Customer Name (mandatory), PIC Name, Customer Phone Number, Customer Email, Address then click “Next” +1. Fill in Tax Type (mandatory) then click "Save" + +* Customer Management page +![customer_management](images/accountReceivable/cust_mgmt_table.png) + +* Create Customer page +![add_cust_1](images/accountReceivable/add_cust_step1.png) +![add_cust_2](images/accountReceivable/add_cust_step2.png) + +All of the created customers can be monitored through your dashboard (Customer List). There are several actions that you take for the customer data: + +| Actions | Functions | +|------|------| +| Edit | To edit the data of the customer | +| Activate/Deactivate | To deactivate / reactivate the customer | + +If you click on the row you selected, you will be able to see the detailed data of the customer, including the list of invoices belonging to that customer. + +![detail_cust_data](images/accountReceivable/detail_cust_data.png) + +![detail_cust_trx](images/accountReceivable/detail_cust_trx.png) + + + +### Amount Customization for Account Receivable + +OY! has a feature that allows you to add the price of the subtotal (addition) and/or deduct the price from subtotal. For example, if you want to apply a discount for your customers, or if you want to add shipping fee,service fee, or any other charges that you would like to add or deduct from the subtotal of the invoice. You may refer to the steps below: + +1. Click "add column" below the subtotal +1. Choose "addition" or "subtraction" from the dropdown +1. Fill in the description +1. Fill in the amount + +* Invoice Creation page +![invoice_creation](images/accountReceivable/invoice_creation_page.png) + +* Add Column page +![add_column](images/accountReceivable/add_column.png) + + +### How to Use Account Receivable via API + +OY! allows invoices creation via API. Here are the steps: + +1. Create the customer data first by sending a request to https://partner.oyindonesia.com/api/account-receivable/customers. Enter the required and optional fields, as referenced in the [API reference docs](https://api-docs.oyindonesia.com/#https-request-create) +1. Then, you can try to create an account receivable by sending a request to https://partner.oyindonesia.com/api/account-receivable/invoices. Enter the required and optional fields, as referenced in the [API reference docs](https://api-docs.oyindonesia.com/#create-amp-send-invoice-api-account-receivable) +1. An endpoint to [check your account receivable invoice](https://api-docs.oyindonesia.com/#https-request-get-invoice-details) data is also available and can be accessed at any time. +1. Lastly, we provide an endpoint to [cancel your account receivable invoice](https://api-docs.oyindonesia.com/#https-request-cancel-invoice) based on id (unique payment id). The invoice must still be active, a payment method must not have been selected, and status must still be in “WAITING_PAYMENT”. + +Just like the account receivable creation from OY! dashboard you can see the transactions and their details from the OY! dashboard. This report will include all payment links generated both via OY! dashboard and API. Data can be differentiate by column “Created Via” on Account Receivable table, if your invoice is created via API then it will fills with “API”, otherwise it will fills with “Dashboard”. + +![ar_table](images/accountReceivable/ar_table_data.png) + + +### Customizing the UI of Payment Link & Account Receivable + +In order to maintain a consistent brand experience for your customers, you can customize the look and feel of both your Payment Link & Account Receivable in the Dashboard, where you can do the following things: + +1. Upload a link address to logo +1. Choose the button color and the theme color of the payment link + +The updated logo will be reflected in both products. + +How to customize the UI via Payment Link or Account Receivable + +1. Log onto your OY! dashboard +1. Go to “Account Receivable” section under “Receive Money” menu +1. Click the “Settings" icon located at the top right of the page +1. Click “Set Appearance” tab +1. Click “Open Configuration” for “Payment Link Display” +1. You will be redirected to the Settings page +1. Input the URL for your logo (if you’re using snipboard.io [https://snipboard.io/], the correct URL should be in “https://i.snipboard.io/image.jpg” format. If you’re using imgbb.com [https://imgbb.com/], the correct URL should be in “https://i.ibb.co/abcdef/image.jpg” format) +1. Select your header color (you can select from our available color picker tools or you can input the 6-digit #HEX code) +1. Select your button and link color (you can select from our available color picker tools or you can input the 6-digit #HEX code) +1. Click "Save" + +![ar_config](images/accountReceivable/ar_table_data_config.png) + +![ar_config_set_appearance](images/accountReceivable/config_set_appearance.png) + +* Payment Link Display Settings + +![PL_config](images/accountReceivable/payment_link_config.png) + + +**Note:** +* By saving the changes, the colors will be applied to the payment links previously created (before saving) as well as the payment links created after saving and also account receivable PDF documents. +* Please check your existing payment link or create a new one. You will see that your changes have been successfully saved. + +### Customizing Account Receivable Invoice Template and Color + +For a more personalized touch on the invoice, you can now customize your template look in the Dashboard, where you can do the following things: +1. Change invoice logo +1. Change Invoice template +1. Change Invoice color + + +How to customize the Account Receivable Invoice UI: +1. Log onto your OY! dashboard +1. Go to Account Receivable section in the dashboard +1. Click the “Settings" icon located at the top right next to “Create New Invoice” button +1. Click “Open Configuration” for “Invoice Template” +1. Input the URL of your logo. Changing the logo here will automatically update the logo in Payment Link, Invoice and 1.Email Notifications. +1. Select your color theme (you can select from our available color picker tools or you can input the 6-digit #HEX code). This color will be the main theme color in your invoice. +1. Select your template from our template selections. Changes will be reflected immediately in the preview area +1. Click “Save” + +* Account Receivable Configuration page + +![ar_config](images/accountReceivable/ar_table_data_config.png) + +![ar_config_set_appearance](images/accountReceivable/config_set_appearance.png) + +* Invoice Template Configuration page + +![invoice_template_config](images/accountReceivable/invoice_template_config_page.png) + +### How to Set Automated Invoice Number + +For your convenience, you have the option to auto-generate invoice numbers. No more worrying about the sequence of invoice numbers. The invoice number template has been pre-defined by OY!. + +1. Log onto your OY! dashboard +1. Go to Payment Link or Account Receivable section in the dashboard +1. Click the 'Settings" icon located at the top right next to “Create New Invoice” button +1. Turn the toggle on. You can now adjust the automatic invoice number format +1. Click “Save” to continue + +* Account Receivable Configuration page + +![ar_config](images/accountReceivable/ar_table_data_config.png) + +* Configuration Invoice Number page + +![ar_config_invoice_no](images/accountReceivable/config_inv_no.png) + + +**Note:** this changes will not impact your previously created invoices + + +### Sending Account Receivable Invoice with Payment Link via WhatsApp + +For your convenience, you can now distribute invoices to your users via WhatsApp using the default message template. If you are interested in using this feature, please kindly contact our business representative or [customer service team](https://www.oyindonesia.com/en/contact-us). + +There will be 2 different ways to distribute the invoice via Whatsapp and there is a maximum of one Whatsapp message per unpaid invoice. When the status is paid, customers will get a payment confirmation from Whatsapp too. + +* Option 1 - via Create New Invoice + +![WA_creation](images/accountReceivable/send_wa_creation.png) + + +* Option 2 - via Invoice Table + +![WA_table](images/accountReceivable/send_wa_table.png) + + +* Option 3 - via Invoice Details + +![WA_detail](images/accountReceivable/send_wa_detail.png) diff --git a/source/includes/en/_business-app.md b/source/includes/en/_business-app.md new file mode 100644 index 00000000000..4d700d0c5cd --- /dev/null +++ b/source/includes/en/_business-app.md @@ -0,0 +1,99 @@ +# OY! Business App + +## OY! Business App + +Great news for you who always in mobility mode but you need to access our dashboard! Now you can access your OY! dashboard from the tip of your finger. OY! Business offers you with easy access to OY! Dashboard, so you can do your financial activities everywhere you are, without opening your laptop or PC. In this app, you can see your balance, account statement, transaction status, send money and also receive money. Yes, doing transaction is now easier than before! + +### Register and KYB + +1. Open your OY! Business app in your Android phone. +1. If it is your first time opening this application, you have to input your phone number. +1. Then, app will shows list of accounts that are tied to the phone number you entered before. +1. If you want to create a new account, tap “Buat Sekarang”. Then follow the instruction. +1. Once you success registering your new account, you can also submit your KYB. + +### Login + +1. Open your OY! Business app in your Android phone. +1. If it is your first time opening this application, you will need to input your phone number to verify your account. + +![OY! Business App input phone number](images/oy_bisnis_app_input_phone_number.jpg) + +1. Next, select which method for sending OTP. Make sure to input the right phone number. Then, please input the OTP number we just sent you. + +![OY! Business App input OTP](images/oy_bisnis_app_otp.jpg) + +1. If the phone number is not yet registed in OY!, you will be required to create a new account. + +![OY! Business App register page](images/oy_bisnis_app_register.jpg) + +1. However, if your phone number have been registered to OY! before, the app will displays list of accounts that are tied to the phone number you entered before. + +![OY! Business App select account page](images/oy_bisnis_app_account.jpg) + +1. Select an account you want to log in to. +1. First time logging in, you will be required to create a new PIN. This PIN will be used to log in to that account in the business app environment. +1. Once you have successfully logged in, you will be directed to homepage. Here, you can see your balance and see your latest transaction. + +![OY! Business App Homepage](images/oy_bisnis_app_new_homepage_done_kyb.png) + + +### Create Payment Link + +1. In the homepage tap “Transaksi Sekarang” button. + +![OY! Business App Homepage](images/oy_bisnis_app_new_homepage_done_kyb.png) + +1. Then, select “Tagih Uang”. + +![OY! Business App Select Transaction](images/oy_bisnis_app_select_transaction.png) + +1. You will see your history of payment links and its transaction history. Then, tap “Buat Link Pembayaran” button in the bottom right. + +![OY! Business App Create a Payment link](images/oy_bisnis_app_buat_link_pembayaran.jpg) + +1. Configure the payment link you want to create and fill all the required details. In this page, you can set the amount method (closed or open amount), admin fee method, payment link expiry date, and payment method(s) you want to provide to your customer(s). Them klik "Simpan". + +![OY! Business App Payment link Configuration](images/oy_bisnis_app_konfigurasi_link_pembayaran.jpg) + +1. Define the amount (if you select closed amount in the configuration page), description, and the transaction ID. Then, tap “Buat Link Pembayaran” button. + +![OY! Business App Submit Payment link](images/oy_bisnis_app_submit_buat_pembayaran.jpg) + + +1. Payment Link has been created and now you can share the link to your customer to receive payment from them. + + +### Send Money (Bulk Disbursement) + +1. In the homepage tap “Transaksi Sekarang” button. + +![OY! Business App Homepage](images/oy_bisnis_app_new_homepage_done_kyb.png) + +1. Then, select “Kirim Uang”. + +![OY! Business App Select Transaction](images/oy_bisnis_app_select_transaction.png) + +1. You will see your bulk disbursement campaign history. Then, tap “Buat Disbursement” button in the bottom right. + +![OY! Business App Create Disbursement](images/oy_bisnis_create_disbursement.jpg) + +1. Create your Bulk Disbursement campaign. First, fill out your campaign details. + +![OY! Business App Campaign Detail](images/oy_bisnis_dibsursement_campaign_detail.jpg) + +1. Next, fill the recipient data (bank, bank account number, amount, recipient email, phone number, note). + +![OY! Business App Recipient Detail](images/oy_bisnis_disbursement_campaign_name.jpg) + +1. Next, check on your recipient list. If you want to add more recipient, click on "Tambah Transaksi" button. + +![OY! Business App Recipient List](images/oy_bisnis_disbursement_recipient_list.jpg) + +1. Confirm your bulk disursement campaign. In this page you can ensure your campaign detail and your recipient data are correct before submitting the campaign. + +![OY! Business App Detail Campaign](images/oy_bisnis_dibsursement_detail_campaign.jpg) + +1. Your Bulk Disbursement campaign has been created and will be displayed on the Bulk Disbursement campaign history page. You may wait for your approver to approve the bulk disbursement so we can execute the transaction. + +![OY! Business App Disbursement Campaign History](images/oy_bisnis_disbursement_campaign_approve.jpg) diff --git a/source/includes/en/_expense-management.md b/source/includes/en/_expense-management.md new file mode 100644 index 00000000000..e477d0fd952 --- /dev/null +++ b/source/includes/en/_expense-management.md @@ -0,0 +1,367 @@ +# Expense Management + +## Corporate Card +OY! Corporate Card product provides the offer to create customized virtual corporate cards that can be used to manage online transactions (e.g. software subscriptions, corporate travel expenses, purchase of supplies, etc.) without hassle. Virtual Corporate Card can be created through the OY! dashboard, therefore no technical integration is required to use this product. Please contact our business representative for further details about this feature. + +### Key Features for Virtual Corporate Card +Feature | Description +------ | ----------- +**Card creation** | You can use the funds directly from your OY! balance for corporate card needs. It is essential to top-up your OY! balance according to your desired card limit. +**Card control** | Create and control the card based on your requirements. You can set the limit amount (in Rupiah), validity period, card renewal frequency and even transaction limitations directly through OY! dashboard. Moreover, you can block and deactivate the card in real-time! Everything on your fingertips. +**Real-time transaction** | Transactions can be tracked easily through OY! dashboard and card holder’s page in real-time. There is no need to wait until the end of month for a full transaction statement. +**Analytics dashboard** | All transactions are recorded and reported in the analytics dashboard, helping companies easily manage and monitor their budgets. + +### Registration and Set Up +Follow the below check-list to ensure you're all set up to use the service: +1. Create an account for OY! business +1. Upgrade your account by submitting the required documentations +1. Have your upgrade request approved +1. Set up your receiving bank account information (note: ensure that the receiving bank account information is accurate as it cannot be changed via OY! dashboard for security reasons) +1. Once your account is approved, you can start using Virtual Corporate Card product + +### Testing +1. Log in to your OY! Dashboard. +1. If you haven’t set approver, please follow steps in How to Set Approver for Virtual Card +1. Select the "Demo" environment. +1. Navigate to "Corporate Card" product under the Expense Management menu. +1. Create a virtual corporate card by follow steps on How to Create Virtual Corporate Card +1. Your approver will receive email regarding approval request to create virtual corporate card, ask them to approve or reject the card for testing purposes +1. After your approver approves the request, the registered cardholder will receive email regarding dummy card information and virtual card status data on the demo OY! dashboard will be updated + +### How to Create Virtual Corporate Card +You can create new virtual corporate card by following these steps: +1. Log in to your OY! dashboard +1. Click “Corporate Card” under Expense Management menu +1. Click “Add New Card” +1. Choose "Virtual" for "Card Type" and usage frequency either single usage or multiple usage and click “Next” +1. Fill in Cardholder details and Card details +1. Once submitted, virtual card will be in “waiting for approval” state +1. After the approval step, the virtual card is ready to be used for transactions. + +**Notes:** Once your OY! balance is transferred to a virtual corporate card, it can only be used for virtual card transactions. + +* Corporate Card Dashboard + + ![Corporate Card Dashboard](images/virtualCard/corporate_card_dashboard.png) + +* Virtual Card Type + + ![Virtual Card Type](images/virtualCard/virtual_card_type.png) + +* Virtual Card Form + +![Virtual Card Form 1](images/virtualCard/vcc_form_1.png) + +![Virtual Card Form 2](images/virtualCard/vcc_form_2.png) + +### How to Transact with Virtual Card +Steps to use virtual card for online transaction: +1. Access your card information (including remaining balance & transaction) via email and enter OTP sent to the phone number registered. +1. Once accessed, input all of your card information into merchant side under “Credit / Debit Card” Option +1. Input 16 digit number, expiry date (MM/YY) and CVV +1. Submit the information and proceed with the transaction and the transaction should be successful. +1. For record purposes, you can upload the invoice for each transaction inside OY! dashboard. + +* Virtual Card Information + +![Virtual Card Information](images/virtualCard/virtual_card_info.png) + +* Virtual Card Transaction Details + +![Virtual Card Transaction Details - Cardholder Page](images/virtualCard/vcc_transaction_details.png) + +![Virtual Card Transaction Details - Email](images/virtualCard/vcc_trx_email.png) + + +**Virtual Card Status** + +Status| Description +------ | ----------- +Pending Approval | Card has been requested but not yet approved. Requests are valid for 14 days. +Active | Card is ready to be used for transactions. +Active with Warning | Card is active with balance, but only <15% balance remaining. +Inactive | Card has been blocked. The card can be activated any time needed through OY! dashboard. +Need top-up | New card has been created but failed to top-up the card balance due to insufficient OY! balance, OR current card limit is 0 and passed renewal time due to insufficient OY! balance. +Expired | Card is expired or intentionally archived permanently. +Rejected | Card is rejected by approver. + + +**Transaction Status** + +Transaction Status | Description +------ | ----------- +Successful | Transaction was successful. +Failed | Failed transaction issue that related to OY! balance (top-up card or create card). +Reversal | Transaction was canceled, and the amount was refunded due to errors, returns, or fraud. +Declined | Transaction was declined by the merchant. +Refund | Refund by merchant. + +### How to Set Approver for Virtual Card +1. Log in to your OY! dashboard +1. Click “Corporate Card” under Expense Management menu +1. During first time product activation, you are required to fill in approver data +1. Fill in the approver details +1. You are required to review and check the T&C, then confirm your approver details +1. Approver will receive confirmation email + +* Add New Approver + +![Add New Approver](images/virtualCard/add_new_approver.png) + +* Approver Form + +![Add Approver Form](images/virtualCard/add_approver_form.png) + +**Notes:** Approver data cannot be added or edited through OY! dashboard for security purposes. Please contact our business representative for help. + +Parameter | Description +------ | ----------- +Name | Approver Name +Position | Approver Role +Phone Number | Approver Phone Number +Email | Approver email for card approval purposes + +### How to Monitor and Manage Virtual Cards +1. Log in to your OY! dashboard +1. Click “Corporate Card” under Expense Management menu +1. Click “See All Cards” +1. Dashboard will show analytics dashboard (divided per department) and list of card to manage +1. Click the card that needs to be managed + +![Virtual Card List](images/virtualCard/vcc_card_list.jpg) + +**Card Actions** + +Card Actions | Description +------ | ----------- +Resend Card Info | To resend card info to cardholders, in case of missing email. +Edit Information | To edit the card limit. Editing card limits will lead to card temporary blockage and require reapproval flow again. +Block | To temporarily lock the card, limit remains in the card and card’s status will be “Inactive” +Archive | To permanently lock the card, card limit will be reduced to 0 and remaining card limit will be returned to OY! balance. +Renew Limit | To renew the card limit with a desired amount using OY! balance. +Resend Approval Notification | To remind approver to approve the card request in case of missing email. +Delete | Only applicable for "Pending Approval" card. This will archive the card so the card is no longer used. + +### How to Set Up Card Configuration +1. Log in to your OY! dashboard +1. Click “Corporate Card” under Expense Management menu +1. Click “Corporate Card Configuration” +1. Select Department / Category / Approver +1. You can choose to whether add new, edit existing or delete +1. Click "Save Changes" + +* Department page prior to “Edit Department” button + +![Department List](images/virtualCard/vcc_department_list.png) + +![Edit Department](images/virtualCard/vcc_edit_department.png) + +* Category page + +![Category List](images/virtualCard/vcc_category_list.png) + +![Edit Category](images/virtualCard/vcc_edit_category.png) + +* Approver page + +![Approver List](images/virtualCard/vcc_approver_list.png) + +### Decline Transaction Possible Reasons + +Issues | Explanations +------ | ----------- +Card utilization is more than requested | Admin requests a card for single use only, but it is being used for more than one transaction. Please request a multiple use card if you expect the card to process multiple transactions. +Insufficient balance on the card | The balance on the card is less than the transaction amount. In this case, the cardholder may need to ask the Admin to top up the card. For example: The card balance is Rp 300,000 but the transaction amount is Rp 302,000. Since the card balance is less than the transaction amount, the transaction will not be processed successfully +Card is inactive | The card is temporarily blocked by the Admin. Cardholder needs to ask the Admin to activate the card. +Card is expired | The virtual card is no longer valid because it has passed its expiration date. Admin or cardholder can check the expired date from dashboard or virtual card information page. +Invalid card number | Cardholder entered the card number incorrectly. Please input the 16 digit card number correctly. +Invalid expiry date | Cardholder made an error entering the card expiry date. Please enter the correct expiry date. +Invalid CVV | Cardholder made an error inputting the CVV number. Please enter the correct CVV. +Issuer network not supported | Not all overseas merchants can process transactions for certain reasons. If you experience a declined error, please check the merchant's capabilities; they might only accept physical cards, regional restrictions, or other reasons. + +Notes + +1. Transactions will be settled according to the bank’s instructions. +1. Successful card transactions will directly reduce card limit. +1. For refunds, please contact the merchant where you made the purchase. OY! is not responsible for processing refunds until we receive the funds back from the merchant. + 1. Refund duration will depend on the merchant and the bank. + 1. Once a refund has been issued, the balance will be returned back to your OY! balance. +1. It is your responsibility to block card usage if you notice any suspicious transactions. + + +## Reimbursement +OY! Reimbursement product offers an easy way to manage employee reimbursement requests and fund disbursements all in one platform. Employees can simply request reimbursement via the link sent to their email. No technical integration is required to utilize the product. + +### Key Features + +Feature | Description +------ | ----------- +Approval Capability | To ensure no fraudulent requests are made, a double approval mechanism exists in the product and is mandatory for the reimbursement process. The first layer is for the team manager via email, and the second layer applies to the admin via the dashboard. Our reimbursement product features a double approval mechanism to ensure integrity. +Disbursement Scheduling | Admin can also immediately schedule the disbursement time after approval from the team manager. Currently, the scheduled disbursement options are 1 day, 3 days, 7 days, and 14 days from the day of admin approval, allowing flexibility in managing cash flows. +Reimbursement Details | For admin, every reimbursement request from employees can be accessed through the OY! dashboard, including the uploaded file, to ensure it matches the requested amount. +Reimbursement Tracking | For employees, no more hassle in checking reimbursement progress with the admin. Your employee will receive a tracking email to check progress in real-time. + +**Notes:** + +* First approval: Team Manager +* Second approval: Admin (it can be Finance Team or HR Team) + +This ensures that each approval request is reviewed by at least two reviewers, providing an extra layer of oversight and security. + +### Registration and Set Up +Follow the below check-list to ensure you're all set up to use the service: + +1. Create an account for OY! business +1. Upgrade your account by submitting the required documentations +1. Have your upgrade request approved +1. Set up your receiving bank account information (note: ensure that the receiving bank account information is accurate as it cannot be changed via OY! dashboard for security reasons) +1. Once your account is approved, you can start using Reimbursement product + +### Testing +1. Log in to your OY! Dashboard. +1. If you haven’t set approver for team manager, please follow steps in How to Set Approver +1. Select the "Demo" environment. +1. Navigate to "Reimbursement" product under the Expense Management menu. +1. Follow steps How to Distribute Reimbursement Link first to able open the reimbursement request page +1. Then create reimbursement request by follow the step How to Fill Reimbursement +1. Your reimbursement request will appear in Reimbursement table (in Demo environment) also sent to registered Team Manager’s email +1. Ask your registered Team Manager to open the link that was sent to their email and approve it for testing purposes. Your team manager can follow steps in How to Approve Transaction. +1. After your team manager approve or reject the transaction, it will reflect on your Demo OY! Dashboard. Afterward, as an admin you can approve or reject the transaction and schedule the disbursement as explained in How to Schedule Disbursement + +### How to Set Approver +By default, admin is the second approver for reimbursement requests. However, you need to register your Team Manager as the first approver for reimbursement requests. **Setting up the approver will only occur once when the page is first opened.** + +1. Log in to your OY! dashboard. +1. Navigate to "Reimbursement" product under the Expense Management menu. +1. Click on "Create Reimbursement Link." +1. Choose "Register Approver." +1. Fill in the approver's name, email address, and department. +1. After registration, the approver will receive a notification via email. + +* Approver registration page + +![Approver registration](images/reimbursement/Approver_Registration.png) + +**Notes** + +* Approver emails are mapped based on department names, and duplicate department names are not allowed. +* After submission, addition, editing, or deletion of existing approvers can only be done via OY! Customer Service. +* Team managers will only receive notifications via email; no dashboard access is required. +* The approver list view is accessible in the dashboard under Reimbursement configuration. + +### How to Distribute Reimbursement Link +After Approver registration, you can start sharing the reimbursement link with employees through two methods: + +1. Via Bulk Upload: + 1. Download the sample file and input a list of employee emails in CSV or XLSX format. + 1. Upload the file for email distribution, then click “Submit”. + 1. Employees will receive the form link in their email and can use it to submit a reimbursement request. +1. Via Link Distribution + 1. Copy the link and distribute it using any convenient method. + 1. Employees may fill the form and proceed to submit a reimbursement request. + +* Bulk Upload and Link Distribution page from OY! Dashboard + +![Distribution page](images/reimbursement/Reimbursement_Link.png) + +### How to Fill Reimbursement Request +1. Click reimbursement link that has been shared from Admin. +1. Fill the Employee Information and Reimbursement Request then click “Submit”. +1. You will receive confirmation email regarding reimbursement request has been submitted + +* Form Reimbursement Request page + +![Form Request page 1](images/reimbursement/reimbursement_form_1.png) + +![Form Request page 2](images/reimbursement/reimbursement_form_2_filled.png) + +**Mandatory Parameters in the Form** + +Parameter | Description +------ | ----------- +Employee Name | Employee identification purposes +Employee Email | This will be used to trigger tracking to employee post-submission +Department | Department will be mapped to approver's email directly +Bank Name | Disbursement bank name +Account Number | Disbursement bank account number. Bank account validity can be checked prior reimbursement submission +Item | Reimbursement item name or description +Amount | Reimbursement total amount (in IDR). Minimum Rp 20.000 +Upload File | Placeholder to upload invoice document. Max 2 file with PDF, JPG, & PNG format (Each file max 5MB). +Transaction Date | Date of transaction printed on the invoice + +**Notes:** you can resend the link anytime in case employees do not receive the email. + +### How to Approve Transaction (Reporting Manager) +1. When a new request is submitted by an employee, the respective team manager will receive a notification and an approver portal link via email. +1. Inside the link, the team manager can find all reimbursement requests with certain statuses (rejected, approved, and need approval). +1. The team manager can choose to either reject the request with a reason or simply approve. +1. Approving the request will trigger an update inside the OY! Dashboard and employee tracker page. + +* Approver portal (unique per approver) + +![Approver portal](images/reimbursement/Approver_Portal_List.png) + +* Approver - request details with action buttons + +![Request details](images/reimbursement/Approver_List_Detail.png) + +### How to Schedule Disbursement +Scheduled disbursement can only be done if the team manager has approved the request, and the OY! dashboard admin agrees to schedule the disbursement. + +1. Open the OY! Dashboard and check the Reimbursement transaction list. +1. Requests with "Need Approval" status mean that the team manager has approved the requests and will require further approval from the dashboard side. +1. OY! dashboard admin can either reject with a reason or approve with a scheduled disbursement day. + +* Request list in dashboard + +![Dashboard list](images/reimbursement/Reimbursement_List.png) + + +* Request detail in dashboard + +![Request Detail](images/reimbursement/Reimbursement_Detail1.png) + +![Request Detail](images/reimbursement/Reimbursement_Detail2.png) + +**Note:** If there is insufficient balance on the day of scheduled disbursement, you can retry the fund transfer manually after a successful OY! balance top up. + + +**Dashboard Status** + +Status | Description +------ | ----------- +Pending Approval | Submitted by employee but no action yet from Team Manager. +Need Approval | Approved by approver but no action yet from OY! dashboard admin. +Canceled | Cancellation can only be performed by the employee. No further action needed. +Completed | Money has been successfully disbursed to employee’s bank account. +Rejected | Rejected by OY! dashboard admin or Team Manager. +Scheduled Payment | Request has been successfully approved, waiting for scheduled disbursement time. +Failed | Disbursement failed due to technical failures. +Insufficient Balance | Fail to disburse due to insufficient OY! balance (OY! dashboard admin can retry payment manually from dashboard after a successful top up of OY! balance). + +### How to Check Reimbursement Progress (Employee) +1. Employees can fill in the reimbursement request form portal via email. +1. Once submitted, the employee will receive a tracking email. +1. Inside the link, employees can find real-time reimbursement progress, from the submission timestamp until disbursement timestamp. +1. Employees can still cancel the request if the team manager has not yet approved. + +* Employee Tracker page + +![Employee tracker](images/reimbursement/Employee_Tracker1.png) + +* Employee Tracker page -- Transaction Detail + +![Employee tracker](images/reimbursement/Employee_Tracker2.png) + +**Tracker Status** + +Status | Description +------ | ----------- +Pending Payment | Request approved but money not yet received +Scheduled Payment | Request has been successfully approved, waiting for scheduled disbursement time. +Rejected | Rejected by admin or Team Manager +Canceled | Canceled by employee +Waiting Approval | Submitted but no action yet from Team Manager or admin +Completed | Money has been disbursed successfully + +**Note:** The tracking email is applicable to each employee per reimbursement request. \ No newline at end of file diff --git a/source/includes/en/_faq.md b/source/includes/en/_faq.md new file mode 100644 index 00000000000..8352370acc7 --- /dev/null +++ b/source/includes/en/_faq.md @@ -0,0 +1,137 @@ +# FAQs + +## API and Bulk Disbursement + +**What are bank maintenance schedules? Will partners be informed?** + +Banks often have regular maintenance schedules which differ from one bank to another. These regular maintenance schedules prevent the execution of transactions to the respective recipient bank during the set period of time. To ensure your convenience, we will queue any transaction requests submitted during the maintenance hours and automatically disburse them once the maintenance is over. + +**What are the amount limits for disbursements?** + +_e-wallet:_ Since each e-wallet provide limits to the amount each user can hold at a time, disbursements made to e-wallet accounts have their respective limits. + +| Wallet | Account Type | Maximum Amount | +| ------ | ------------- | -------------- | +| OVO | OVO Club | Rp 2.000.000 | +| OVO | OVO Premier | Rp 10.000.000 | +| DANA | DANA Verified | Rp 2.000.000 | +| DANA | DANA Premium | Rp 10.000.000 | +| GoPay | Unverified | Rp 2.000.000 | +| GoPay | Verified | Rp 10.000.000 | + +**What are the minimum Amount for disbursements?** + +_e-wallet:_ Since each e-wallet provide limits to the amount each user can hold at a time, disbursements made to e-wallet accounts have their respective limits. + +| Wallet | Minimum Amount | +| ------- | -------------- | +| OVO | Rp 10.000 | +| DANA | Rp 10.000 | +| GoPay | Rp 10.000 | +| Linkaja | Rp 10.000 | + +**Is there a cut-off time?** + +No, we are available 24/7 including holidays. + +**Is there a maximum transaction volume and transaction amount in a day?** + +There are no daily limits of how many bulk campaigns can be created and executed. There is also no limit to the number of total transactions per disbursement campaign. + +**How many recipient emails can I send the transaction notification to?** + +You can send up to 5 emails per transactions with a limit of 255 characters total. For each transaction, simply list out the email recipients. + +**What are the supported banks for the disbursement products?** + +We support transactions to 100+ banks in Indonesia. Please refer to the bank codes [here](https://api-docs.oyindonesia.com/#disbursement-bank-codes) when using our disbursement products. (Please note that we currently do not support disbursements to Virtual Accounts.) + +**Can I specify the "notes" to be reflected in the beneficiary bank account statement?** + +Yes. However, we only support notes for these 7 banks: BCA, BNI, BRI, CIMB, DBS, Mandiri, and Permata. However, please note that should there arise unexpected difficulties with the connection to these aforementioned banks, our failover system will not be able to support these notes to be reflected in the beneficiary bank account statement. + +**Will the funds accepted from the API VA Aggregator and Payment Link and Invoice products be readily available for disbursement uses?** + +Yes. The funds accepted from the API VA Aggregator and Payment Link and Invoice products will be automatically reflected in your OY! balance in real-time, allowing you to use these funds directly for disbursement purposes. + +**[Bulk Disbursement Specific] What if the recipient name on the xlsx or CSV file is different from the bank account name? What is the phone number used for?** + +As long as the bank account number is valid and not dormant, the transaction will still be executed. + +The name and phone number are visible only to the partner and is used for the partner’s own documentation. The name and phone number listed are not used by OY or sent to the recipient. + +**[API Disbursement Specific] Are the disbursements performed in real-time?** + +Yes. Disbursements executed through our API Disbursement are all performed in real-time. + +## API VA Aggregator + +**What are the supported banks?** + +We currently have 11 available banks for our API VA Aggregator. Please refer to the bank codes [here](https://api-docs.oyindonesia.com/#va-aggregator-bank-code). + +**Is the amount received in realtime?** + +Yes, all the amount received are in realtime and will be immediately available in your OY! balance. + +## Payment Link/Invoice + +**What are the possible payment methods for users?** +We support payments via bank transfers, credit card, debit card, and QR code from the following: + +* Bank Transfer via Virtual Account: BCA, BNI, BRI, CIMB Niaga, Mandiri, Permata Bank. + +* Bank Transfer via Unique Code: BCA + +* Credit Card/Debit Card: VISA, Mastercard + +* E-Wallet: ShopeePay, DANA, LinkAja, OVO + +* QR Code: QRIS + +**What are closed and open amounts? What happens when the amount paid by the user is different from the declared amount in the created Payment Links?** + +A closed amount is a configuration so that the payment link or invoice can only be paid if the actual declared amount is paid. The user will not be able to pay any amount other than the declared amount. + +An opened amount is a configuration so that the payment link or invoice can be paid up to the declared amount (or any, if amount is not declared). If the user pays an amount that is different from the declared amount, the payment link will remain active. The payment link will only reflect a completed status when the full amount is paid in total. + +**What is the difference between Bank Transfer via Virtual Account and Bank Transfer via Unique Code?** +Bank Transfer via Virtual Account (VA) will generate specific account number destination for each transaction. You can create an open amount or closed amount transaction using VA. Detail explanation of VA can be seen [here](https://docs.oyindonesia.com/#va-aggregator-accepting-payments). You can create VA Transactions via [API Payment Routing](https://docs.oyindonesia.com/#va-aggregator-accepting-payments) or [VA Aggregator](https://docs.oyindonesia.com/#va-aggregator-accepting-payments) + +Bank Transfer via Unique Code generates unique code for each transaction but the account number destination will always be the same. The total amount paid is subtracted by the unique code. For example, your end user wants to paid a transaction of Rp 100.000 and get Rp 100 as the unique code. The payment uses subtraction approach, so your end user will pay a total of Rp 99.900 to complete the payments. Unique Code also have limitations compared to VA, where you can only create unique code transaction during the operational hours (3 AM - 8.30 PM GMT+7). + + +## Payment Routing +**What are the possible payment methods for users?** +We support payments via bank transfers, e-wallet , credit card/debit card, and QR code from the following: + +* Bank Transfer via Virtual Account: BCA, BNI, BRI, CIMB Niaga, Mandiri, Permata Bank. + +* Bank Transfer via Unique Code: BCA + +* Credit Card/Debit Card: VISA, Mastercard + +* E-Wallet - One Time: ShopeePay, DANA, LinkAja, OVO + +* E-Wallet - Direct Payment: ShopeePay + +* QR Code: QRIS + +**What is the difference between Bank Transfer via Virtual Account and Bank Transfer via Unique Code?** +Bank Transfer via Virtual Account (VA) will generate specific account number destination for each transaction. You can create an open amount or closed amount transaction using VA. Detail explanation of VA can be seen [here](https://docs.oyindonesia.com/#va-aggregator-accepting-payments). You can create VA Transactions via [API Payment Routing](https://docs.oyindonesia.com/#va-aggregator-accepting-payments) or [VA Aggregator](https://docs.oyindonesia.com/#va-aggregator-accepting-payments) + +Bank Transfer via Unique Code generates unique code for each transaction but the account number destination will always be the same. The total amount paid is subtracted by the unique code. For example, your end user wants to paid a transaction of Rp 100.000 and get Rp 100 as the unique code. The payment uses subtraction approach, so your end user will pay a total of Rp 99.900 to complete the payments. Unique Code also have limitations compared to VA, where you can only create unique code transaction during the operational hours (3 AM - 8.30 PM GMT+7). You can create Unique Code Transaction via [API Payment Routing](https://docs.oyindonesia.com/#va-aggregator-accepting-payments) or [Payment Link](https://docs.oyindonesia.com/#payment-links-invoice-accepting-payments) + +**What are the differences between E-Wallet One Time Payment and E-Wallet Direct Payment?** +E-Wallet One Time creates a payment URL that can be paid by any guest users. Once payment URL is opened, your end user's is redirected to the E-Wallet app and completes the payment inside the E-Wallet's app. + +E-Wallet Direct creates a payment URL dedicated to a particular user. The end user needs to link their E-Wallet account first by doing [Account Linking](https://docs.oyindonesia.com/#feature-account-linking-accepting-payments). Once the end user has linked their account to your app, you can initiate a Direct payment. OY will return an authorization URL for the end users to input the E-Wallet PIN and complete the payment. Using Direct payment, your end users will complete the transaction inside your app. Therefore, it will bring a better experinence for your end users. + +| | One Time | Direct | +| --- | -------- | ------ | +| Supported E-Wallets | ShopeePay, LinkAja, DANA, OVO | ShopeePay | +| Need to do Account Linking first? | No | Yes | +| Can be created via.. | API Payment Routing
Payment Link
API E-Wallet Aggregator | API Payment Routing | +| Send phone number in API Create Request | Optional | Mandatory | +| Who can complete the transaction? | Any Users/Guest | Dedicated User.
Only the user whose phone number listed in the API request | +| Payment Completion Journey | Inside E-Wallet App | Your app | \ No newline at end of file diff --git a/source/includes/en/_home.md b/source/includes/en/_home.md new file mode 100644 index 00000000000..b024414803f --- /dev/null +++ b/source/includes/en/_home.md @@ -0,0 +1,151 @@ +# Home + +Welcome to OY! We aim to be among the world class financial services and always put our customers first in everything that we do. Armed with receive money (accepting payments) and send money (fund disbursement) products and services, we are ready to help your business achieve higher growth through swift and secure payment infrastructure solutions. + +## How OY! Can Help + +![How OY Can Help](images/how_oy_can_help.png) + +## Our Product Suites + +**Money-Out (Disbursement)** + +* API Disburse +* Bulk Disbursement + +**Money-In (Accepting Payments)** + +* Virtual Account (VA) Aggregator +* Payment Link Page/Invoice + +## Registration Process + +Registration process begins with creating an account. Creating an account is 100% free and enables you to try our product suites in staging/test environment, where you can execute money-in and money-out dummy transactions (without real money movement). + +After successfully creating an account, if you are interested to go live (execute real money-in and/or money-out transactions) or integrate with OY!, you are required to upgrade your account by submitting supporting legal documentations. + +## Creating Account + +Follow the steps below to create an account: + +**1. Register on the OY! dashboard**: To create your account, please register [here](https://business.oyindonesia.com/register?), and complete the form with all the required information. + +*Please note that your username cannot be changed once the registration form is submitted.* + +![Creating Account](images/desktop_register.png) + +**2. Activate your account**: Once the registration is submitted, an activation link will be sent to the email registered. +Once you are successfully logged in, you will be able to try our product suites in staging/test environment, where you can execute money-in and money-out dummy transactions (without real money movement). +If you are ready to proceed to go live or integrate with OY!, refer to the below sections for more information on the steps to upgrade your account. + +![Creating Account](images/creating_account_2.jpg) + +Once you are successfully logged in, you will be able to try our product suites in staging/test environment, where you can execute money-in and money-out dummy transactions (without real money movement). + +If you are ready to proceed to go live or integrate with OY!, refer to the below sections for more information on the steps to upgrade your account. + +## Verifying Your Business + +Follow the steps below to verify your business: + +**1. Request to Verify your Business**: To access the full version of our products and services, please select “Verify Your Business” on the menu at the left side of the OY! dashboard (Production environment) and complete all the required information. + +![Upgrade](images/upgrade_account_1.png) + +**2. For Individual User: Upload ID Card for Identity Verification**: Please take an identity card photo and a Selfie photo holding an identity card. The system will automatically read your Identity card photo to fill the form, make sure all information is correct based on your ID card. + +- For Individual type of business + ![Upgrade](images/upgrade_account_2.png) + +**2. For Corporate User: Upload the Legality Documents**: We ask for different legality documents for different types of corporate entities. After choosing your corporate type we will show you what document you need to submit. + +![Upgrade](images/upgrade_account_3.png) + +Please upload the required supporting documents according to your business type. Documents are only accepted in PDF format and must be less than 10 MB in size each. + +- Here is an example for Corporate (PJSP) type of business + ![Upgrade](images/upgrade_account_4.png) + +- For Corporate (Non PJSP) type of business + ![Upgrade](images/upgrade_account_5.png) + +**3. Fill Business Information Form**: Let us know more about your business by filling out the Business Information form. Please note that you will not be able to change this field in the need revision status. + +![Upgrade](images/upgrade_account_7.png) + +**4. Submit your Bank Account**: Add Bank Account Information as the destination account for withdrawing money from OY! Business Platform. + +*Please note that the receiving bank account information cannot be changed via the OY! dashboard once your business verification request is approved for security reasons. Please contact us at partner@oyindonesia.com to change the receiving bank account information.* + +![Upgrade](images/upgrade_account_6.png) + +**5. Submit your Request**: Click “Submit”, and a new status of “Waiting for Approval” should appear on the Home page, indicating that your request has been submitted along with your supporting documents. + +This process should take about 1-2 working days. In the event that the status of your request is **Need Revision** or **Rejected**, please keep reading below. Otherwise, you are good to go! + +**Status: Need Revision** + +Upon reviewing the upgrade request, we might ask for additional documentations. Please follow the steps below: + +**1. Read our report**: A report will be sent to your email regarding the information and documents that will need to be revised or added. + +**2. Refill out the form**: We will show you which part of the form and document that will need to be revised. When submitting another request to verify your business, please resubmit all your supporting documents. Documents are only accepted in PDF format and must be less than 10 MB in size. + +**3. Submit your request**: Click “Submit & Request to Upgrade”, and a new status of “Upgrade Account Requested” should appear on this page, indicating that your request has been submitted along with your supporting documents. + +Please note that after 3 or more revision processes, you will need longer waiting time to submit your next verification request. We will inform you via email or dashboard when you can do the next submission. + +For further inquiries, please contact us at [partner@oyindonesia.com](partner@oyindonesia.com) and our representative will get in touch. + +**Status: Rejected** + +For further inquiries regarding this review, please contact us at [partner@oyindonesia.com](partner@oyindonesia.com) and our representative will get in touch. + +## Required Supporting Documents + +Below is the list of documentation to be submitted for an upgrade request: + +- For Individual type of business + +1. ID Card + +- For Corporate (PJSP) type of business + +1. Company’s Nomor Pokok Wajib Pajak (NPWP) +2. Surat Izin Usaha Perdagangan (SIUP)/ Tanda Daftar Perusahaan (TDP) / Nomor Induk Berusaha (NIB) +3. SK Kemenhukam +4. Legalized Akta Perubahan Terakhir +5. Legalized Akta Pendirian Perseroan +6. Director’s National ID Card (KTP) +7. Shareholder Structure +8. License from BI/OJK +9. Pengesahan Kementerian Terkait + +- For Corporate (Non PJSP) type of business + +1. Company’s Nomor Pokok Wajib Pajak (NPWP) +2. Surat Izin Usaha Perdagangan (SIUP)/ Tanda Daftar Perusahaan (TDP) / Nomor Induk Berusaha (NIB) +3. SK Kemenhukam +4. Legalized Akta Perubahan Terakhir +5. Legalized Akta Pendirian Perseroan +6. Director’s National ID Card (KTP) +7. Shareholder Structure +8. Pengesahan Kementerian Terkait + +## Completion/Go-Live Checklist + +Once you're ready to execute real money-in and money-out transactions or integrate with OY!, follow the below check-list to ensure you're all set up: + +1. Create an account + +2. Upgrade your account by submitting the required documentations + +3. Have your upgrade request approved + +4. (Required if you want to use our API) Submit your IPs and callback URLs (both for staging and production environment) to your business representative or to partner@oyindonesia.com + +5. (Required if you want to use our API) Request your staging and production API Key to our business representative (note: you are not required to upgrade your account to request staging API Key. Upgrade is only required if you want to request Production API Key). + +6. (Optional) Perform testing. We recommend that you thoroughly test before going live. We’ve provided a Staging Mode in our dashboard, where you can test transactions to understand how our products work, without using real money. If you are a developer, you can also test your integrations. + +7. (Optional) If your company wants to have multiple users: Create additional sub-account users under User Management diff --git a/source/includes/en/_oy-tutorial.md b/source/includes/en/_oy-tutorial.md new file mode 100644 index 00000000000..47867340953 --- /dev/null +++ b/source/includes/en/_oy-tutorial.md @@ -0,0 +1,619 @@ +# OY! Dashboard Tutorial + +## Login + +To be able to login to Dashboard; + +1. Go to https://desktop-business.oyindonesia.com/login. +2. Make sure the username and password that you input are correct. + +![Login page](images/desktop_login.png) + +3. You will be redirected to the input the OTP page. The OTP will be sent to the email associated with your username. Then, input the OTP number. + +![Login OTP](images/desktop_login_otp.png) + +![Email OTP](images/email_otp.png) + +If you tick the 'Remember this device' option, you are not required to input an OTP when you log back in next time. Hence, point 2 is required only when you login for the first time after you create your account. + +## Dashboard Analytics + +Analytics feature allows you to get some insights about your earning and spending transaction that happened in OY!. With this feature, you can get information about how much money you have spent and how much money you have earned in a time interval. You can see your transaction growth on daily, weekly, or monthly basis. You also can select the time range. You will get some insights about the trend of your business transaction. Therefore, this feature can help you to generate new business decision to grow your business. + +![Dashboard Analytics Image](images/desktop_analytics_spend_earn.png) + +* You can select which time granularity and time period you want to see you transaction growth with. +* The increase and decrease indicator will compare your transaction performance from the time period you select vs the previous period. +* If you put your cursor in a line point, the number of transaction volume will be displayed. + +For VA, Payment Link, and E-wallet users. Now you can see the comparison of conversion between each payment method your end users are using. This data can be a great insight for you so you know which channel most of your users are using. + +![Dashboard Analytics Payment Method](images/desktop_analytics_paymethod.png) + +## User Management + +If you have a Super Admin role, you are able to add account for your team members and define the role for them. + +These are the following steps to add new users; + +1. Login to Dashboard. +2. Go to Accounts -> User Management. +3. Click the 'Create User' button + +![User Management 1](images/user_management_1.png) + +4. Fill in the full name, username, email, phone number (optional), and password + +![User Management 2](images/user_management_2.png) + +5. Choose the role that you want to assign your team member(s) to. You can select between Admin, Approver, and Maker + +Aside from adding new users, a Super Admin is also able to edit and delete their existing users through clicking edit/delete button on the User List table. + +Here are the access control matrix in OY! Dashboard: + +Dashboard features + +| Task | Superadmin | Admin | Approver | Maker +|-------|-------|-------|-------|-------| +|Upgrade Account| Can upgrade | Can upgrade | - | - | +|Analytics| Can view | Can view | Can view | Can view | +|Account Statement| Can view and export | Can view and export | Can view and export | Can view and export | +|Settlement Report| Can view and export | Can view and export | Can view and export | Can view and export | +|Top up Balance | Can top up | Can top up | Can top up | Can top up | +|Withdraw Balance | Can withdraw | Can withdraw | Can withdraw | Can withdraw | +|Add User | Can add user | Can add user | - | - | +|User Profile| Can view and edit | Can view and edit | Can view and edit | Can view and edit | +|Business Profile| Can view and edit | Can view and edit | Can view and edit | Can view and edit | +|Authenticator OTP| Has permission | Has permission | Has permission | Has permission | +|Add Payment Method | Can add | Can add | - | - | +|Set up auto report | Can set up | Can set up | - | - | +|Set up auto withdrawal| Can set up | Can set up | - | - | +|Set up multi approval| Can set up | Can set up | - | - | +|Set up notification settings| Can set up | Can set up | - | - | +|Set up developer options| Can set up | Can set up | - | - | +|Add transaction category| Can add | Can add | - | - | +|Config Transaction Receipt | Can config | Can config | Can config | Can config | + +Multi Entity Management + +| Task | Superadmin | Admin | Approver | Maker +|-------|-------|-------|-------|-------| +|Create Sub-entity| Can create| Can create | - | - | +|Top up Sub-entity via Dashboard| Can top up| Can top up | - | - | +|Disconenct Sub-entity| Can disconnect | Can disconnect | -| -| + +Bulk Disbursement Product + +| Task | Superadmin | Admin | Approver | Maker +|-------|-------|-------|-------|-------| +|Create Disbursement Campaign| Can create| Can create | - | Can create | +|Approve Disbursement Campaign| Can approve| Can approve | Can approve | - | +|View and Export Bulk Disbursement Report| Can view and export | Can view and export | Can view and export | Can view and export | + +Claim Fund Product + +| Task | Superadmin | Admin | Approver | Maker +|-------|-------|-------|-------|-------| +|Create Claim Fund | Can create| Can create | - | Can create | +|Approve Claim Fund| Can approve| Can approve | Can approve | - | +|View and Export Claim Fund Report| Can view and export | Can view and export | Can view and export | Can view and export | + +Account Payable Invoice + +| Task | Superadmin | Admin | Approver | Maker +|-------|-------|-------|-------|-------| +|Create AP Invoice | Can create| Can create | - | Can create | +|Approve AP Invoice | Can approve | Can approve | Can approve | - | +|View and Export AP Report| Can view and export | Can view and export | Can view and export | Can view and export | + +Payment Link One time and Reusable Product + +| Task | Superadmin | Admin | Approver | Maker +|-------|-------|-------|-------|-------| +|Create Payment Link| Can create| Can create | Can Create | Can create | +|View and Export Payment Link Report| Can view and export | Can view and export | Can view and export | Can view and export | + +Virtual Account + +| Task | Superadmin | Admin | Approver | Maker +|-------|-------|-------|-------|-------| +|Create VA via Dashboard| Can create| Can create | Can Create | Can create | +|View and Export VA Report| Can view and export | Can view and export | Can view and export | Can view and export | + +E-Wallet Aggregator + +| Task | Superadmin | Admin | Approver | Maker +|-------|-------|-------|-------|-------| +|View and Export E-Wallet Report| Can view and export | Can view and export | Can view and export | Can view and export | + +Account Receivable Invoice + +| Task | Superadmin | Admin | Approver | Maker +|-------|-------|-------|-------|-------| +|Create AR Invoice | Can create| Can create | Can create | Can create | +|View and Export AP Report| Can view and export | Can view and export | Can view and export | Can view and export | + +API Inquiry + +| Task | Superadmin | Admin | Approver | Maker +|-------|-------|-------|-------|-------| +|View and Export API Inquiry Report| Can view and export | Can view and export | Can view and export | Can view and export | + +API Transaction Data + +| Task | Superadmin | Admin | Approver | Maker +|-------|-------|-------|-------|-------| +|View and Export API Transaction Data Report| Can view and export | Can view and export | Can view and export | Can view and export | + + +## 2-Factor Authentication + +To increase the security of your account, you can set up 2-factor Authentication. Currently, the security method that OY provides is through an authenticator app + +These are the following steps to set up the 2-factor authentication; + +1. Login to Dashboard. +2. Go to Accounts -> Authenticator OTP. +3. Download Authenticator App in your smartphone (from Play Store/App Store) or PC (you can download an extension for your browser). Examples of the app: Google Authenticator, Microsoft Authenticator, Authy, etc +4. Scan the barcode displayed on your OY Dashboard with your Authenticator App OR enter the setup key displayed next to it on your Authenticator App. +5. Please type in the 6-numerical code displayed on your Authenticator App in the 'Authenticator OTP' on OY Dashboard. + +## Top Up + +Top up is used to add balance to your OY account. + +Here's how you can access the top-up menu on your dashboard; + +1. Login to Dashboard. +2. Go to Transaction Report -> Account Statement. +3. Click the 'How To Top Up' button. There are 2 ways in which you can top up your OY account: + +### Top Up via Virtual Account + The Virtual Account number information can be found on 'How to Top Up Balance via VA' tab once you click the 'How To Top Up' button. If you choose to top up via VA, the topped up amount will be credited into your account real-time and you do not need to send any manual confirmation to OY + +![VA Topup](images/desktop_topup.png) + +### Manual Top Up via Bank Transfer + Aside from Virtual Account, you can also transfer the top up money to OY's giro account. If you choose to top up via this method, you need to perform a manual confirmation in order that your money can be credited into your account. + +After you have completed the fund transfer to OY! Indonesia, you need to perform the following steps; + +1. Click the 'Manual Top Up Confirmation' tab on the top-up menu + +![Manual Topup](images/manual_topup.png) + +2. Fill in the fields with the following information; + +| Field Name | Description | +| --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Amount | The amount that you have topped up (as per written in the Bukti Transfer) | +| Beneficiary Bank | A dropdown where you can choose to which bank you have transferred the top up amount to | +| Transfer Receipt | Click the upload button to upload the Bukti Transfer obtained after you have successfully performed the transfer. Can be in PDF, PNG, or JPEG format, with max. file size 10 MB | +| Transfer Receipt Type | Transfer Receipt with Reference Number --> If your uploaded transfer receipt displays the reference number, you need to choose this option. Transfer Receipt without Reference Number --> If your uploaded transfer receipt doesn't display the reference number, you need to choose this option and fill in the date & timestamp according to your Bukti Transfer. | + +3. Click 'Submit Now' +4. You will receive a receipt of your transaction in your email, stating that your transaction is currently being processed + +![Topup Confirmation Dashboard](images/topup_confirmation_dashboard.png) + +![Topup Confirmation Email](images/topup_confirmation_email.png) + +5. You will receive an email confirming whether your top up is success or failed + +![Success Failed Confirmation](images/success_failed_confirmation.png) + +### Top Up using BCA with Unique Code + Now, we provide you with a new method for BCA. Top-up using BCA with a unique code allows you to top up your balance in a real-time manner. You do not have to confirm manually as we will automatically detect your top-up and reflect it to your balance. + +Steps: + +1. In the top up page, click on the “BCA Unique Code” tab. +2. Input the amount you want to top up. Note : The minimum amount that we allow for this method is IDR10,000. + +![BCA Unique Code Input Amount](images/desktop_bcauniquecode_input_amount.png) + +3. OY! will display the amount you input PLUS three-digits unique code generated by us. Transfer the exact nominal that appeared on the screen to the bank account stated on the page. Please make sure to use BCA bank account. + +![BCA Unique Code Transfer](images/desktop_bcauniquecode_transfer.png) + +4. After the transaction is successful, the ORIGINAL AMOUNT will be reflected in your balance. +Note : The operational hour for this method is every day, from 3.01 AM to 8.00 PM. Outside those hours, this method will be closed. + +![BCA Unique Code Transfer](images/desktop_bcauniquecode_success.png) + +## Withdrawal + +When you want to withdraw, do the following steps: + +1. Go to Transaction Report -> Account Statement +2. Click Withdraw +3. Fill in the amount that you want to withdraw + +![Withdrawal Top Up](images/withdrawal_topup.png) + +4. Choose your withdrawal type. You can pick one from the following options: + + - Instant with admin fee -> This means that your withdrawal will be processed and arrive into your receiving bank real-time. If the amount to be withdrawn is <= IDR 50 million and the receiving banks are BCA, BRI, BNI, Mandiri, Permata, DBS and CIMB Niaga, you can choose this option. You will be charged an admin fee if you choose this option + + - Manual -> This means that your withdrawal will be processed up to 2 business days. If the amount to be withdrawn is > IDR 50 million OR the receiving banks are not BCA, BRI, BNI, Mandiri, Permata, DBS or CIMB Niaga, you have to choose this option. You will NOT be charged an admin fee if you choose this option. + +### Setting Up Auto Withdrawal Process + +If you want to set up an **Auto Withdrawal** periodically, go to Settings -> Auto Withdrawal. + +![Auto Withdrawal](images/auto_withdrawal.png) + +Here's how you can configure the auto withdrawal settings; + +| Field Name | Description | +| ------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Auto Withdrawal Schedule | The schedule of your auto withdrawal. Options available: Daily, Weekly, Bi-Weekly, Monthly | +| Start Date | Start date of your auto withdrawal schedule | +| Ends | Options available: Never -> if the auto withdrawal will go on for an indefinite time, By Date -> if the auto withdrawal will end on a specific date. If you choose this option, you need to fill in the date on which you plan to end the auto withdrawal process | +| Amount | Amount to be automatically withdrawn. Options available: Maxiitym amount -> Withdraw the maximum available balance, Specified Amount -> custom specified amount. Note: Maximum Amount is recommended for Auto Withdrawal transactions as withdrawals will not be processed if the specified amount is greater than the OY balance when the withdraw scheduler runs | +| Withdrawal Type | Options available: 1) Instant, or 2) Manual. | +| Email | An email we will send the notification to. Use (;) between email to differentiate them (Max. 5 emails can be inputted) | + +## Transaction Report - Settlement Report + +Settlement Report stores the list of transactions corresponding to payment methods that are not settled on a real-time basis (delayed settlement). For example, if for VA BCA the settlement time is H+2, each new VA BCA transaction performed by your customers will appear on the Settlement Report. + +To access the Settlement report; + +- Login to Dashboard +- Go to Transaction Report -> Settlement Report + +![Settlement Report](images/settlement_report.png) + +The Settlement Report consists of the following information; + +- Total Amount to be Settled Today: This tells you the sum of amount scheduled to be settled to your account statement balance today +- Total Delayed Settlement Amount: This tells you the sum of amount not yet settled to your account statement. (the settlement status is still not SUCCESS) +- The Settlement Transaction List Table + +| Field Name | Description | Example | +| ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------ | +| Transaction Date | The date on which your customer performs the transaction | 11 May 2021 | +| Transaction Time | The time at which your customer performs the transaction | 17:44:09 | +| Transaction ID | A unique transaction ID given by OY | d4b26687-34b9-43d3-9d08-af440bcbaca7 | +| Partner Transaction ID | A unique transaction ID that is assigned by you for a given transaction | TXID_001 | +| Product | The product associated with a given transaction. This will be filled with VIRTUAL_ACCOUNT if the transaction comes from Virtual Account Aggregator product, or PAYMENT_CHECKOUT if the transaction comes from Payment Link product | VIRTUAL_ACCOUNT | +| Payment Method | The payment method associated with a given transaction. Possible values: VA [Bank Name], CARDS, QRIS, EWALLET SHOPEEPAY | VA BCA | +| Transfer Amount | The transaction amount (before getting deducted with admin fee | +Rp 10.000 | +| Admin Fee | The admin fee associated with a given transaction | -Rp 1.000 | +| Total Amount | The transaction amount that has been deducted with admin fee | +9,000 | +| Settlement Date | The scheduled settlement date for a given transaction | 12 May 2021 | +| Settlement Time | The scheduled settlement timestamp for a given transaction | 15:00:00 | +| Settlement Status | The settlement status for a given transaction. Possible values: WAITING (if the amount is not yet settled to your account statement balance), SUCCESS (if the amount has been settled to your account statement balance). | SUCCESS | + +If the Settlement Status of a transaction is still WAITING, it will not yet appear as a row in your Account Statement report and the amount will not yet be added in your Account Statement balance. + +If the Settlement Status of a transaction is already SUCCESS, it will appear as a row in your Account Statement report and the amount will be added in your Account Statement balance. + +### Callback for Delayed Settlement (Non-Real Time Settlement) + +If your settlement is non-real time, for every transaction whose payment method is settled H+>0, you will receive two callbacks with details as follows: + +1. 1st Callback -> To be sent after your customer successfully executes the transaction. For example, if your customer executes the transaction on 11 May 2021 at 14:00:00, that is also when we send the 1st Callback to you. In the 1st callback, the settlement status is set to WAITING (because it is not yet settled to your Account Statement balance) +2. 2nd Callback -> To be sent after the settlement status is changed from WAITING into SUCCESS. For example, if the settlement status is changed into SUCCESS on 12 May 2021 at 15:00:00, that is also when we send the 2nd Callback to you. In the 2nd callback, the settlement status is SUCCESS + +### Capability to Export and Filter the Settlement Report + +Export: You are able to export/download the settlement report in CSV, PDF, and XLSX. + +Filter: You are able to filter the settlement report by transaction date (range), product, payment method, settlement date (range), and settlement status + +## Notification + +You can enable or disable notification settings for low balance, send money products and receive money products if you have a super admin or admin role: + +1. Login to dashboard https://business.oyindonesia.com/ +2. Go to Settings -> Notification + ![Notif Disbursement - 1 Initial](https://user-images.githubusercontent.com/79620482/126457509-ca20c24b-9277-4be4-943e-801b79806e65.png) +3. If you want to notified by email if your balance is low, set in the "Low Balance" tab. Input the amount threshold and the email where the notification will be sent to. +4. Choose whether or not you want notifications enabled or disabled for +Bulk Disbursement or API Disbursement. + a. Enable Notification: Email notification will be sent for pending, failed and success transactions. + b. Disable Success Notification: email notification will only be sent for pending and failed transactions. +5. Choose whether or not you want notifications enabled or disabled for +Payment Link, or VA. + a. Enable Notification: Email notification will be sent for success transactions. + b. Disable Success Notification: No email notification will be sent. +6. The email field: allows the user to add, remove, and edit email notification receivers in this column, which can hold up to three email addresses. +7. Click ‘Save Changes’ + +## Multi Entity Management + +Multi Entity Management is a feature that can help you handle complex relations between entities in your business in the OY! Dashboard. You can connect to other entities in the form of Main Entity - Sub Entities (1 to N relationship). The one who become the Main Entity will have a capability to oversee te sub-entities' transaction and execute transaction using its sub-entities' balance. Please contact our Business Development if you’re interested to use this feature. +With this feature you will be able to : + +1. Have a detailed report on transactions performed by all of sub-entity account. +2. Distribute balance between the main entity account to sub-entity account. +3. Use sub-entity account balance to disburse funds. +4. Receive money using Payment Link on behalf of sub-entity's username. +5. Free admin fee to disburse between related user. + +### **Registration & Setup** + +**Prerequisites** + +- Register an account on the [OY! dashboard](https://business.oyindonesia.com/register?) +- Activate your account through the activation link sent via email +- Upgrade your account +- Upgrade request is approved +- Contact our representatives to enable the feature + +### **How To Use** + +To properly used Multi Entity Management, we required several accounts to be linked together. Each account can be used independently, and there will be some requirements that you should fulfil in order to use this feature. Please contact our representatives for more information. + +**A. Link to Sub-entity Account** + +1. **For Main Entity Account** + +To add a new sub-entity,please 1) Log in to Dashboard with main account credential 2) Go to Multi Entity → List of Sub-entity + +![Image: As a Main Entity - Add a New Sub-entity](../images/MEM_Add_Sub_Entity_1.png) + +1. Click the “+ Add Sub-Entity +2. Fill in the username for the sub-entity you wished to connect. You can find your username from the menu Accounts → User Profile in the Username section +3. Click Add and then if the account is found in the system you can confirm by clicking Connect in the confirmation window + +![Image: As Main Entity - Subs - 6. Add Child - Type Username](../images/MEM_Add_Sub_Entity_2.jpg) +After confirming, the request will be sent to the respective account to be accepted + +1. **For Sub-entity Account** + +To receive a request from a main entity , please 1) Log in to dashboard with child credential 2) Go to Transaction Report → Account Statement +![Image: As Child - AccState - 2b. Waiting approval](../images/As_Child_AccState_2b_Waiting_approval.png) + +1. Click the “Check All request” button that can be found near the top right corner of the page +2. You will see the incoming request to be the main entity account for your account, then you can choose which account is the correct account for the main entity by click Accept in the respective account and confirmation window + +![Image: As Child - AccState - 4b. 2 Requests to connect.png](../images/As_Child_AccState_4b_2_Requests_to_connect.png)![Image: As Child - AccState - 4e. Confirmation to connect](../images/As_Child_AccState_4e_Confirmation_to_connect.png) + +3. After successfully received the main entity request to connect, you will find your parent information near the top right corner of your page, and you are now connected with your main entity. + +![Image: As Child - AccState - 4h. Success add new head company](../images/As_Child_AccState_4h_Success_add_new_head_company.png) + +**B. Topup money to a sub-entity account** + +After connected with your sub-entities, you can transfer money to the sub-entity account by accessing the menu Multi Entity → List of Sub-entity +1. Find the account that you wished to top-up +2. Click the “+ Top Up” button +3. Fill in the amount you wish to top-up +4. Click the “Top Up Now” button to proceed +5. You can also do Top Up by transfer to one of the VA provided (see “How to Top Up Balance via VA” + +![Image: As Parent - Subs - 5a. Top Up.png](../images/As_Parent_Subs_5a_Top_Up.png) + +**C. Disburse Money using a sub-entity's balance** + +In Multi Entity Management, you will be able to do disbursement using your sub-entity account on their behalf. To do this you could + +1. Access the Bulk Disbursement menu and clicking on “+ Create Disbursement” button +2. Select your sub-entity balance as a source of fund to do disbursement by selecting “My Sub-entity Balance” and choose the appropriate account. +3. After choosing the balance you could proceed to do disbursement just like regular disbursement + +![Image: Choose SoF - 2. Choose SoF subsidiary.png](images/MEM_select_subentity_disbursement.png) + +**D. Create a payment link on behalf of a sub-entity account** + +With this feature, you will be able to accept payment from your users through Payment Link created on behalf of your sub-entity account. When your users make a successful transaction, the transaction will be recorded in the Sub-entity Account's balance. As a main entity, you are equipped with the ability to view the Sub-entity Account's balance and transaction list anytime through Multi Entity → Sub-entity Statement. + +Follow the below steps to create a payment link on behalf of a sub-entity account: + +***Via API*** + +Hit [API Create Payment Link](https://api-docs.oyindonesia.com/#api-create-payment-link-fund-acceptance) and fill in "child_balance" parameter with the username of a sub-entity account that you will set as the balance destination for the transaction. When your users make a successful transaction, the transaction will be recorded in the specified Sub-entity Account's balance. + +***Via Dashboard*** + +1. Click Payment Link -> One-time (if you want to create a one-time payment link) or Click Payment Link -> Reusable (if you want to create a reusable payment link) +2. Click "Create One-Time Link" (for one-time payment link) or click "Create Reusable Link" (for reusable payment link) +3. You will see a pop-up to proceed with the creation process +4. Fill in "Balance Destination" with "My Balance" (if the balance destination of the transaction is your own) or "Sub-entity Balance" (if the balance destination of the transaction is your sub-entity's). If you select "Sub-entity Balance", you will see a dropdown to select a username of the sub-entity account. Only 1 sub-entity account is currently allowed to be a balance destination +5. If you select "Sub-entity Balance", when your users make a successful transaction, the transaction will be recorded in the specified Sub-entity Account's balance. + +![MAM Payment Link](images/MEM_select_subentity_paymentlink.png) + +## Xero Integration + +Xero is an accounting platform that is mainly used by companies. We have integrated our system to Xero system in order to help you optimize your workflow. By connecting your OY! account to your Xero account, you do not have to login to your Xero account. Means that you do not have to manually record your transaction that happened in OY! to Xero. You will get seamless experience of recording your transaction. + +### How does it works? + +Once you have connected your Xero account in OY!, any transaction that happened in OY! will be automatically recorded to your Xero tenant account. Upon successful connection, we will trigger creation of new Bank Account, which is OY! Balance Bank Account. Any transaction that happened in OY! will be recorded in that bank account, to SPEND or RECEIVE money transaction. Then, you will be required to map your Xero’s Chart of Account. Mapping your Chart of Accounts will help us to put your transaction into a correct Account. + +### Connect your Xero + +- Login to your dashboard. In the sidebar, select Integration menu. +![Integration Menu](images/xero/menu.png) + +- You will see Xero menu. Click “Connect” to connect your Xero account to OY!. You will be redirected to Xero’s login page. Once you have successfully logged in to Xero, a notification will appear. +![Product List](images/xero/product_list.png) + +Preview for Xero's login page +![Xero's Login Page](images/xero/login_xero.png) +![Consent Page](images/xero/consent_page.png) + +Preview for Notification +![Notification](images/xero/connect_success_notification.png) + +- Upon success connect to Xero, we will create a new Bank Account into your Xero account, named “OY! balance” in which any transaction happened in OY! will be recorded there. +![New Account](images/xero/new_account.png) + +### Map your Chart of Account + +- Once you have successfully connected to Xero, you will be directed to a page to map your Chart of Account. +![Configure Account Page](images/xero/coa_mapping.png) + +- Then, map each product to the corresponding Account. Any transaction using each product will be put into the chart of account you defined in this page. +![Mapping Modal](images/xero/coa_mapping_modal.png) +![Mapping Modal](images/xero/coa_mapping_modal_2.png) + +Note: Special for Bulk Disbursement product, you can define the Chart of Account later in the campaign creation process - Step 3 Input Detail page. In the Transaction Category field. + +![Bulk Disbursement CoA](images/desktop_accountingapp_bulkdisbursement.png) + +- You can change the CoA mapping anytime as needed, just go to Integration >> Xero >> Configuration. + +### Record Transaction to Xero + +- Now, your OY! account has been connected to your Xero account and each products has been mapped to its corresponding Chart of Account. +- Any success transaction that happened in OY! will be recorded to Xero with the corresponding Account you defined in Mapping Account menu. +- Transaction in OY! will be treated as SPEND or RECEIVE money transaction in a bank account (OY! Balance). +- Each transaction will contains of two rows. Row 1 contains of amount of transaction where row 2 contains of admin fee. +- In this version, any tax will be excluded. Means that you will need to input manually the tax invoice you obtained from OY! team to your Xero. +![Recorded Transaction](images/xero/recorded_transaction.png) +![Recorded Transaction](images/xero/recorded_transaction_2.png) + + +### Disconnected your Xero + +1. To disconnect your Xero account from OY!, open the Integration menu on the sidebar. Then select “Disconnect”. +2. Then, you will be disconnected from Xero. Any transaction that you execute via OY! will not be recorded into Xero. + +## Accurate Integration +Accurate is a local accounting platform that is mainly used by companies in Indonesia. We have integrated our system to Accurate system in order to help you optimize your workflow. By connecting your OY! account to your Accurate account, you do not have to log in to your Accurate account. This means that you do not have to manually record your transaction that happened in OY! to Accurate. You will get a seamless experience of recording your transaction. + +### How does it works? +Once you have connected your Accurate account in OY!, any transaction that happened in OY! will be automatically recorded to your Accurate account. Upon successful connection, we will trigger the creation of a new Bank Account, which is OY! Balance Bank Account. Transactions that happened in OY! will be recorded in that bank account, to PEMASUKAN or PENGELUARAN module. Then, you will be required to map your Accurate’s Chart of Account (in Accurate, it is Akun Perkiraan). Mapping your Chart of Accounts will help us to put your transaction into the correct Account. + +### Connect your Accurate +1. Login to your OY! dashboard. In the sidebar, select the Integration menu. +2. You will see Accurate menu. Click “Connect” to connect your Accurate account to OY!. You will be redirected to Accurate’s login page. + +![Connect Accurate](images/desktop_accurate_connect.png) + +3. After login to your Accurate account, click Allow to allow conection between OY! and Accurate. +![User Consent Accurate](images/desktop_accurate_authorize.png) + +4. Next, please select an Accurate database that you want to connect with your OY! account. +![Accurate Select Database](images/desktop_accurate_select_database.png) + +5. Once you have successfully connected to Accurate, a notification in your OY! dashboard will appear and you can see your status is now "Connected/Terhubung". + +![Accurate Connected](images/desktop_accurate_connected_status.png) + +6. Upon success connecting to Accurate, we will create a new Bank Account into your Accurate account, named “OY! balance” in which any transaction that happened in OY! will be recorded there. Your current OY! balance will also be mapped there. + +7. In your Accurate Dashboard, you can see that OY! Indonesia app will be listed in the Accurate Store >> Aplikasi Saya menu. +![Accurate Listing](images/desktop_accurate_listing.png) +![Accurate Aplikasi saya](images/desktop_accurate_aplikasisaya.png) + +8. Note: A fee of IDR 20k/month will be added to your Accurate billing. + +### Map your Chart of Account (Akun Perkiraan) + +1. Click on "Configure" to map your Chart of Account (Akun Perkiraan). +2. Then, map each product to the corresponding Account. Any transaction using each product will be put into the Chart of Accounts you defined on this page. + +![Accurate Config COA](images/desktop_accurate_config_coa.png) + +Note: Special for Bulk Disbursement product, you can define the Chart of Account later in the campaign creation process - Step 3 Input Detail page. In the Transaction Category field. + +![Bulk Disbursement CoA](images/desktop_accountingapp_bulkdisbursement.png) + +3. You can change the CoA mapping anytime as needed, just go to Integration >> Accurate >> Configure. + + +### Record Transaction to Accurate +1. Now, your OY! account has been connected to your Accurate account and each product has been mapped to its corresponding Chart of Account. +2. Any successful transaction that happened in OY! will be recorded to Accurate with the corresponding Account you defined in the Mapping Account menu. +3. Transactions in OY! will be recorded in Pengeluaran or Pemasukan module, under OY! Balance bank account. It will also credited/debited your OY! Balance Bank Account. +4. Note: For some reasons, we do not map Top up and Withdraw balance transactions to Jurnal. Therefore, you should adjust your bank’s Cash Bank account balance as well as OY! Indonesia Cash Bank account balance in your Accurate dashboard everytime you execute top up and withdraw transactions. + + +### Disconnected your Accurate +1. To disconnect your Accurate account from OY!, open the Integration menu on the sidebar. Then select “Disconnect”. + +![Accurate Connected](images/desktop_accurate_connected_status.png) + +2. Then, you will be disconnected from Accurate. Any transaction that you execute via OY! will not be recorded in Accurate. +3. Note: To remove the bill, please make sure you also uninstall OY! app in your Accurate dashboard. + + +## Jurnal Integration +Jurnal is a local accounting platform that is mainly used by companies in Indonesia. We have integrated our system to the Jurnal system in order to help you optimize your workflow. By connecting your OY! Account, you do not have to manually record your transaction that happened in OY! to Jurnal. You will get a seamless experience of recording your transaction. + +### How does it work? +Once you have connected your Jurnal account in OY!, any transaction that happened in OY! will be automatically recorded to your Jurnal account. Upon successful connection, we will trigger the creation of a new Bank Account, which is OY! Balance Bank Account. You will be required to map your Jurnal’s Chart of Account. Mapping your Chart of Accounts will help us to put your transaction into the correct Account. Transactions that happened in OY! will be recorded in that bank account, into the EXPENSES or SALES INVOICE modules. + +### Connect your Jurnal +1. Before connecting your Jurnal account, you need to log in to your Jurnal account in your browser. + +![Jurnal Login](images/jurnal_login.png) + +2. Login to your OY! dashboard. In the sidebar, select the "Integration" menu. + +3. You will see the Jurnal menu. Click “Connect” to connect your Jurnal account to OY!. + +![Jurnal Connect](images/jurnal_integration_menu.png) + + +4. There will be a page to ask for your consent regarding this connection process. Make sure to select OY! Indonesia. Click Allow to allow connection between OY! and Jurnal. + +![Jurnal Authorization](images/jurnal_authorization_page.png) + +5. Once you have successfully connected to Jurnal, a notification in your OY! dashboard will appear and you can see your status is now "Connected/ Terhubung". + +![Jurnal Connection Success](images/jurnal_connection_success.png) + +![Jurnal Notification](images/jurnal_success_notif.png) + +![Jurnal Connection Status](images/jurnal_connection_status.png) + +6. Upon success connecting to Jurnal, we will create a new Bank Account into your Jurnal account, named “OY! Balance”. Any transaction that happened in OY! will be recorded under that bank account. Your current OY! balance will be mapped into your OY! Balance Cash Bank Account as a bank deposit transaction. + +![Jurnal OY! Balance](images/jurnal_oy_balance.png) + +### Map your Chart of Account +1. Click on "Configure" button in the "Integrasi" menu to map your Chart of Account. + +2. Then, map each product to the corresponding Account. Any transaction using each product will be put into the Chart of Accounts you defined on this page. + +![Jurnal COA Mapping](images/jurnal_coa_mapping.png) + +3. Note: Special for Bulk Disbursement product, you can define the Chart of Account later in the campaign creation process - Step 3 Input Detail page. In the Transaction Category field. + +![Jurnal Bulk Disburse COA](images/jurnal_bulk_disburse_coa.png) + +4. You can change the CoA mapping anytime as needed, just go to Integration >> Jurnal >> Configure. + +5. Note: currently, we do not include mapping and recording the PPN tax. The applied tax will be recorded to Admin Fee Chart of Account. We will update this document as soon as possible once we have included mapping PPN function. + +### Record Transaction to Jurnal +1. Now, your OY! account has been connected to your Jurnal account and each product has been mapped to its corresponding Chart of Account. +2. Any successful transaction that happened in OY! will be recorded to Jurnal with the corresponding Account you defined in the Mapping Account menu. Transactions from OY! will be recorded in the Expenses or Sales Invoice module, under OY! Balance bank account and it will credited/debited your OY! balance bank account. Note: Admin Fee of Receive Money transactions will be recorded in the Expenses module. + +3. In this image, we show you a Disbursement Transaction with CoA "Iklan & Promosi" and admin fee with CoA "Komisi & Fee". It is recorded into the "Expenses" module in your Jurnal account with status LUNAS. + + +4. Note: For some reasons, we do not map Top up and Withdraw balance transactions to Jurnal. Therefore, you should adjust your bank’s Cash Bank account balance as well as OY! Indonesia Cash Bank account balance in your Jurnal dashboard everytime you execute top up and withdraw transactions. + +### Disconnected your Jurnal +1. To disconnect your Jurnal account from OY!, open the Integration menu on the sidebar. Then select “Disconnect”. +2. Then, you will be disconnected from Jurnal. Any transaction that you execute via OY! will not be recorded in Jurnal. + + + +## OY!'s Authorization Callback +When you successfully complete a transaction using OY!'s API-based product, OY! will send a callback to your system. To keep your system secure, we have provided a feature that allows you to control and approve these callbacks from OY! before they are received by your system. + +Currently, we only support configuration for the OAuth 2.0 protocol. If your system uses OAuth 2.0, you can set up OY! callbacks to be directed to an authorization process before they are received by your system. To do this, simply enter the Authorization URL, client ID, and client secret via your dashboard. + +### How to Setup Authorization Callback +You can follow the steps below to set up callback authorization via your dashboard: + +1. Log in to the OY! dashboard as a super admin or admin, then select the "Settings" menu, and choose "Developer Options." +2. Click on the "Authorization Callback" tab. Then, you should input your authorization details (Authorization URL, client ID, and client secret). This step is necessary to allow OY! to complete the authorization process before sending a callback to you. + +![Callback Authorization Configuration](images/oy_auth_configuration.png) + +3. Click the "Save changes" button. +4. Once you have configured your OAuth 2.0 credentials, all callbacks from OY! will go through your authorization process before you can receive them. OY! will obtain an access token along with the expiry time from your system. +5. If you no longer want callbacks from OY! to go through the authorization process, you can remove all the OAuth information or credentials you've entered on this page and then click "Save Changes." + diff --git a/source/includes/en/_responsecodes.md b/source/includes/en/_responsecodes.md new file mode 100644 index 00000000000..23f7c8cc5e5 --- /dev/null +++ b/source/includes/en/_responsecodes.md @@ -0,0 +1,8 @@ +# Response Codes + +Possible status codes on the Payment Result Callback: + +Payment Status | Type | Meaning +-------------- | ---- | ------- +success | String | Payment by Buyer is successful and has been sent to your bank account +failed | String | Payment by Buyer is failed \ No newline at end of file diff --git a/source/includes/en/_sending-payments.md b/source/includes/en/_sending-payments.md new file mode 100644 index 00000000000..c14f0894a00 --- /dev/null +++ b/source/includes/en/_sending-payments.md @@ -0,0 +1,1364 @@ +# Sending Payments + +## API Disbursement + +API disbursement product provides the capability for you to disburse to 100+ banks in Indonesia via OY! at any time. The integration process to use the API disbursement product is straight forward and the details can be checked [here](https://api-docs.oyindonesia.com/#fund-disbursement). + +### Key Features + +**Overbooking** +OY! can use the funds directly from your Mandiri or CIMB bank accounts for your disbursement needs. You will only need to top up the admin fee needed to execute the disbursements instead of the full amount of your disbursement. Please contact our [business representative](partner@oyindonesia.com) for further details about this feature. +**Check Transaction Status and Callback** + +For all disbursements executed, you will receive notifications regarding your transaction whether it is successful, failed or pending. We also provide an API for you to check the transaction status manually. IP proxy is also available upon request to enhance the security and integrity of the callback you will receive. + +**Check Balance** + +You can check your available balance at anytime to ensure that you have sufficient balance to execute a disbursement. + +### Registration and Set Up +**Prerequisites** + +* Register an account on the [OY! dashboard](https://business.oyindonesia.com/register?) + +* Activate your account through the activation link sent via email + +* Upgrade your account + +* Upgrade request is approved + +* Provide IP to be whitelisted and callback link to our business team + +* Receive an API Key from us + +* Integrate with our [API](https://api-docs.oyindonesia.com/#fund-disbursement) + +### Testing + +Once you successfully create an OY! account, you can immediately simulate disbursement via API. +Follow the below steps to test the flow: + +1. Create an account +2. Login into the dashboard +3. Change the environment to “staging” +4. Once the environment changed to staging, there will be API key staging available on the bottom left corner of the page +5. Before creating a disbursement transaction, check your available balance through API GET https://api-stg.oyindonesia.com/api/balance +6. Create a disbursement by sending a ‘POST’ request to _https://api-stg.oyindonesia.com/api/remit_ (https://api-stg.oyindonesia.com/api/remit) using your staging API key. Enter the required and optional fields, as referenced in the API reference docs (https://api-docs.oyindonesia.com/#disbursement-fund-disbursement) +7. Fill in the amount, recipient bank, recipient account, and the partner transaction-id +8. To get the status of a disbursement request, you can call the API https://api-stg.oyindonesia.com/api/remit-status, This API also offers the option for callback status under field send_callback +9. If payment is successful or failed, we will send a callback to the registered staging callback URL destination. Callback URL can be registered via our business representative. +10. The API disbursement transactions can be monitored through OY! dashboard from the “Send money - API disbursement” menu. + + +### How to Use + +In order to create disbursements, a sufficient available OY! balance is required in the account. More details and instructions about topping up to your OY! account coming soon. + +Before you execute a disbursement, you can verify the beneficiary account information from our [inquiry endpoint](https://api-docs.oyindonesia.com/#bank-account-inquiry). + +> Below is an example of the request body for inquiry: + +```shell +curl -X POST https://partner.oyindonesia.com/api/inquiry +-H 'content-type: application/json, accept: application/json, x-oy-username:myuser, x-api-key:987654' +-d '{ + "recipient_bank": "022", + "recipient_account": "7823023345" + }' +``` + +> It will return an [error message](https://api-docs.oyindonesia.com/#fund-disbursement-response-codes) if the request is not valid. Otherwise, below is the sample response parameters that will be returned: + +```json +{ + "status":{ + "code":"000", + "message":"Success" + }, + "recipient_bank":"022", + "recipient_account":"7823023345", + "recipient_name":"Budi Budianto Budiman", + "timestamp":"16-10-2021 09:55:31" +} +``` + +> + +Next, send a request body to execute a disbursement request to be sent to our [disbursement endpoint](https://api-docs.oyindonesia.com/#disbursement). + +> Below is an example of the request body for the remit: + +```shell +curl -X POST https://partner.oyindonesia.com/api/remit +-H 'content-type: application/json, accept: application/json, x-oy-username:myuser, x-api-key:7654321' +-d '{ + "recipient_bank": "022", + "recipient_account": "7823023345", + "amount":100000, "note":"Pembayaran Nov IV", + "partner_trx_id":"Tx15048563JKFJ", + "email" :"budi.s@gmail.com" + }' +``` + +> Below is the sample response parameters that will be returned: + +```json +{ + "status":{ + "code":"101", + "message":"Request is Processed" + }, + "amount":100000, + "recipient_bank":"022", + "recipient_account":"7823023345", + "trx_id":"89718ca8-4db6-40a0-a138-a9e30d82c67d", + "partner_trx_id":"Tx15048563JKFJ", + "timestamp":"16-10-2019 10:23:42" +} +``` + +> + +An enpoint to [check the transaction](https://api-docs.oyindonesia.com/#get-disbursement-status) is also available and can be accessed at anytime. + +> Below is an example of the request body: + +```shell +curl -X POST https://partner.oyindonesia.com/api/remit-status +-H 'content-type: application/json, accept: application/json, x-oy-username:myuser, x-api-key:7654321' +-d '{ + "partner_trx_id": "1234-asde", + "send_callback": "true" + }' +``` + +> The above command returns a JSON structured similar like this: + +```json +{ + "status":{ + "code":"000", + "message":"Success" + }, + "amount":125000, + "recipient_name":"John Doe", + "recipient_bank":"008", + "recipient_account":"1234567890", + "trx_id":"ABC-456", + "partner_trx_id":"1234-asde", + "timestamp":"16-10-2020 10:34:23", + "created_date": "24-01-2020 06:48:08", + "last_updated_date": "24-01-2020 06:48:39" +} +``` + +> + +A callback with the following information will be sent to the callback endpoint that you can register with us. + +> Below is an example of the request body: + +```shell +curl -X POST https://partner.oyindonesia.com/api/remit-status +-H 'content-type: application/json, accept: application/json, x-oy-username:myuser, x-api-key:7654321' +-d '{ + "partner_trx_id": "Tx15048563JKFJ" + }' +``` + +> Below is the sample response parameters that will be returned: + +```json +{ + "status":{ + "code":"000", + "message":"Success" + }, + "amount":100000, + "recipient_name":"Budi Soemitra Nasution", + "recipient_bank":"022", + "recipient_account":"7823023345", + "trx_id":"89718ca8-4db6-40a0-a138-a9e30d82c67d", + "partner_trx_id":"Tx15048563JKFJ", + "timestamp":"16-10-2019 10:40:23", + "created_date": "16-10-2019 10:23:42", + "last_updated_date": "16-10-2019 10:34:23" +} +``` + +> + +You can also [check your balance](https://api-docs.oyindonesia.com/#get-balance) anytime to ensure you have sufficient balance from our endpoint. + +> Below is an example of a request body to check the balance: + +```shell +curl -X GET 'https://partner.oyindonesia.com/api/balance' +-H 'Content-Type: application/json' +-H 'Accept: application/json' +-H 'X-OY-Username: janedoe' +-H 'X-Api-Key: 7654321' +``` + +> Below is the sample response parameters that will be returned: + +```json +{ + "status":{ + "code":"000", + "message":"Success" + }, + "balance":100000000.0000, + "pendingBalance":2000000.0000, + "availableBalance":98500000.0000, + "overbookingBalance":98500000.0000, + "timestamp":"10-12-2019 12:15:37" +} +``` + +> + +Lastly, all transactions can be monitored from the OY! dashboard which includes all the transaction details. + +![API Disbursement](images/api_disburse_error_reason.png) + +![API Disbursement](images/api_disburse_success.png) + + +For further details on the parameters definition and proper usage, please refer to our [API Documentation](https://api-docs.oyindonesia.com/#fund-disbursement). + + +## Bulk Disbursement + +Our Bulk disbursement product provides the capability to execute disbursements to multiple beneficiaries with a single xlsx or csv file upload ("Campaign") up to 25,000 transactions. Bulk disbursement is made through the OY! dashboard, where details regarding the disbursement campaign can be found. No technical integration is required to use this product. + +### Key Features + +**Overbooking** +OY! can use the funds directly from your Mandiri or CIMB bank accounts for your disbursement needs. You will only need to top up the admin fee needed to execute the disbursements instead of the full amount of your disbursement. Please contact our [business representative](partner@oyindonesia.com) for further details about this feature. + +**Account Management** + +When you first create an account, your account will be assigned as a Super Admin role. As a Super Admin, you have the ability to create new sub-accounts and assign different roles to your team such as Admin, Maker and Approver that are applicable for bulk disbursement. The Super Admin and Admin can also edit or delete created sub-accounts. + +*Note: it is not necessary to create new sub-accounts in order to use bulk disbursement. The Super Admin and Admin roles allows you to directly create and approve bulk disbursements.* + +Detailed step-by-step instructions on setting up user management and the different role types coming soon. + +**Multi-Layer Approval** + +Multi-layer Approval will improve your control over your bulk disburse transaction especially for big amount of money. You can setup up to 3 layers of approver before the transaction instruction is executed. By assigning proper approver and amount limitation, you can avoid a huge trouble on your business operational caused by incorrect transfer amount. + +**Overall Campaign Summary** + +Keep track of all the details of the entire campaign such as the total amount of disbursement, total number of transactions, and the maker and approver related information of a campaign. + +**Transaction Details** + +Itemized details of each individual transaction, including their respective statuses: success, pending, or failed. + +### Registration and Set Up +**Prerequisites** + +* Register an account on the [OY! dashboard](https://business.oyindonesia.com/register?) + +* Activate your account through the activation link sent via email + +* Upgrade your account + +* Upgrade request is approved + +### Testing + +1. Log on your OY! dashboard +2. Choose "Staging" environment +3. Click "Send Money" menu, and choose "Bulk disbursement" +4. Click "Create Disbursement" +5. Fill in the necessary details by following the steps explained in the “How to Use” section + +### How to Use + +In order to create disbursement campaigns, a sufficient available OY! balance is required in the account. If there is an insufficient available balance in the account, campaigns can still be created but not approved. + +*1. Create Disbursement*: On the OY! dashboard, navigate to Send Payments > Bulk Disburse on your left menu bar. Click “Create Disbursement” on the far righthand side of that page to create a new bulk disbursement campaign. + +*2. Create Campaign Details*: Fill in the campaign details with 2 options: + +![Create Bulk - 1 Initial](https://user-images.githubusercontent.com/79620482/128454459-16770aa6-486a-40b8-a73b-f2c4bb1910e3.png) + + +a. upload an xlsx or csv file + +Please upload an xlsx or csv file with each individual transaction’s details of your bulk disbursement campaign. An example template for both file types are available for download on the OY! dashboard. The following list of items are required in your CSV file. + +Column | Description | Example +------ | ----------- | ----------- +Name | Recipient Name | Budy +Email | Recipient Email (can contain up to 5 emails with a total maximum of 255 characters, incoming transaction notifications will be sent to these emails) | Budi@email.com +Amount | Amount in IDR (only numbers) | 100000 +Bank Code | [Destination Bank](https://docs.oyindonesia.com/#disbursement-bank-codes) | 014 +Bank Account Number | Recipient Bank Account Number | 12341234 +Phone Number | Recipient Phone Number | 62812341234 +Notes (Optional) | Transaction Notes | + + b. add disbursement detail manually. : +choose ‘add disbursement detail manually’ and fill out a campaign name and campaign description in the provided spaces. These details are strictly used as your tracking information only and will not be shared to the transaction recipients. + +c. [Staging only] You can replicate failed status on individual transaction within the campaign by fill in _Bank Account Number_ value with `3000000`. Another value will be processed normally. + +*3. Re-verify all the Information and Submit*: Once your xlsx or csv file is uploaded or filled out manually, you can verify all of the information uploaded from the file from the table displayed. If there is any incorrect submission such as invalid entry due to special characters, a red box will appear to highlight the entry that should be corrected. Issues must be resolved before a campaign can be submitted + +![Create Bulk - 2 File Uploaded](https://user-images.githubusercontent.com/79620482/128454606-2838f6ca-aad1-446a-becb-75d780460708.png) + + +*4. Validate Name Matching* +After all the issue has been resolved, user able to click submit and there will be popup shown to validate each recipient *name *with their *Bank Account Name* as shown below: + +![Create Bulk - 3 Ask Name Validation](https://user-images.githubusercontent.com/79620482/128454745-c5a42979-30ba-44c2-a405-bbe9b0325677.png) + +if you choose YES: if there is a name difference, a popup name validation with details of mismatched transactions will be displayed. if the information inputted is invalid, you could edit the information and choose the ‘ validate’ button to revalidate the data, or you could click the ‘ignore mismatch’ button to ignore the name matching validation and to process the disbursement. + +![Create Bulk - 5e Mismatch Names](https://user-images.githubusercontent.com/79620482/128454900-3a007579-2701-4305-9f9d-15cc8a09ab2f.png) + + +If there is no issue with the details uploaded, a validate and submit button will be available at the bottom of the list of transactions, indicating that all information is valid. Click “Submit” to complete creating the bulk disbursement campaign. + +![Create Bulk - 6a All data inquired without Name Matching](https://user-images.githubusercontent.com/79620482/128454982-b1ab6dfc-c279-4baf-a0e6-a2983a069488.png) + + +*5. Approve/Cancel Campaign**: Once the bulk disbursement campaign is created, a new status of `waiting approval` will appear. Approve the campaign by clicking the “Approve” button. If you want to cancel a campaign, click the “...” button and select “Cancel”. + +![Bulk Disburse](../images/bulk_disburse_4.png) + +Once the bulk disbursement campaign is approved, details regarding the campaign can no long er be changed. This includes changes made to individual transactions and their respective recipient information. + +The balances will also immediately reflect changes. For more information about the different types of balances, click here. You will also receive an email with the campaign information summary (“Outgoing Transfer Alert”) when transactions are executed. + +*Note:* +- *Depend on the approval layer that you configured, this transaction should be approved by all layer before it can be executed by the system.* +![Bulk Disburse](../images/multi-layer-approval.png) + +- *Multiple campaigns can be approved at a time as long as there is sufficient available balance to complete campaigns that have already been approved but are still in queue to be processed.* + +*6. Keep Track of Campaign Details**: To check the details of the bulk disbursement campaign, click on the campaign name to find the campaign summary and its recipient list. Keep track of the both the overall campaign status and the status of individual transactions through the page. + +![Bulk Disburse](../images/bulk_disburse_5.png) + +*7. Status: In-Progress, Finish, and Cancel**: Congratulations! You just made your first bulk disbursement with OY! Below are a list of statuses you will find on the OY! dashboard. + +_In-Progress_ + +As your individual disbursements are executed, the status of your bulk disbursement campaign will indicate an in-progress status. + +![Bulk Disburse](../images/bulk_disburse_error_reason.png) + +_Incomplete_ + +The status of your bulk disbursement will change to incomplete once all of the listed transactions have been executed and the relevant final statuses of Failed or partially Failed have been assigned. The failed transactions will be shown the failed reason and can be retried. +![Notif Pending - 1 Initial](../images/bulk_disburse_list_incomplete.png) + + +_Finish_ + +Once all of the listed transactions have been completed and the final status of success for all transactions has been achieved, the status of your bulk disbursement campaign will change to Finish. The recipients should have all received an email detailing an “Incoming Transfer Alert.” You can also download a report of the campaign details directly through the OY! dashboard. +![Bulk Disburse](../images/bulk_disburse_finish.png) + +_Cancel_ + +If you choose not to approve your disbursement campaign, the status of your bulk disbursement campaign will indicate a cancelled status. + +![Bulk Disburse](../images/bulk_disburse_cancel.png) + +You can also double check each of your transactions by navigating to the account statement page on the OY! dashboard. + + +## Claim Fund +Claim Fund product enable you to do disbursement without knowing your recipient bank account at first. You will simply create a link for them to fill-out bank account information and the payment will be processed by our system. +This feature will remove you from the hassle of collecting your customer information manually then doing multiple bank transfer. +Best use of this feature is : refunds, reimbursement claim, any disburse transaction in which the destination is not your regular partner. + +At the moment, Claim Fund product is available only on OY! Business Dashboard. + +### Transaction Flow + +![Transacation Flow](../images/claim-fund-flow.png) + +### Use Case +1. Refund for purchase transaction +2. Any money transfer transaction where you don't have recipient bank information + +### Registration and Set Up + +**Prerequisites** + +* Register an account on the OY! dashboard (https://business.oyindonesia.com/register?) +* Activate your account through the activation link sent via email +* Upgrade your account +* Upgrade request is approved + + +### Testing +1. Log on your OY! dashboard +2. Choose "Staging" environment +3. Click "Send Money" menu, and choose "Claim Fund" +4. Click "Create Claim Fund" +5. Fill in the necessary details by following the steps explained in the “How to Use” section + +### How to Use +In order to execute claim fund transaction successfully, a sufficient available OY! balance is required in the account. However, if there is an insufficient available balance, claim fund transaction can still be created but the approval will failed. + +**1. Business Dashboard - Create Claim Fund** + +* Create Claim Fund: On the OY! dashboard, navigate to Send Money > Claim Fund on your left menu bar. Click `Create Claim Fund` button on the far righthand side of that page to create a new claim fund transaction. +![Claim Fund Landing Page](../images/claim-fund-landing.png) +![Create Claim Fund - input data](../images/claim-fund-create.png) + +* Please fill-out the information accordingly. Below table is the description of each fields: + +| Column | Description | Example | +|------------------------------------ |------------------------------------------------------------------------------------------------------------------------------------------------------------------- |-------------------- | +| Amount to Claim | Amount of money to be sent | 1000000 | +| Expiration Duration | How long does this claim link be active. After expiration time, customer will not be able to submit their information then new claim fund link has to be created. | 12 Hours | +| Set as default expiration duration | Select this option to make it default expiration time for the next claim fund transaction. | - | +| Partner Transaction ID | Unique identifier for the recipient. | CF00001 | +| Note | additional remarks for recipient | Refund transaction | +| Recipient Name | Recipient Name | Dwiki Dermawan | +| Email | Recipient Email | dwiki@gmail.com | + +* Click `Create Claim Fund` button to submit the transaction. Your recipient will get notified of this claim fund transaction through email. Transaction link will be attached on this email. + +* Successful claim fund transaction will be listed on the claim fund transaction listing with INITIATED status. +![Create Claim Fund - Success](../images/claim-fund-create-success.png) + +* Please be noted that this transaction still need account detail to be filled-out by the recipient. + +**2. Fund Recipient - Input Account Information** + +* On the notification email, user click the `Ajukan Klaim Dana` link to get into claim fund input page. +![Create Claim Fund - Email](../images/claim-fund-user-email.png) + +* User should fill-out the detail information so that OY! system can continue with the approval process. +![Create Claim Fund - input detail](../images/claim-fund-input-detail.png) +![Create Claim Fund - input submitted](../images/claim-fund-input-submitted.png) + +**3. Business Dashboard - Approve Transaction** + +Transaction need to go through approval process to ensure that the money will be delivered to correct recipient and sufficient amount is available. + +* Approve claim fund transaction: On the OY! dashboard, navigate to Send Money > Claim Fund on your left menu bar. Transactions that already have user detail will be marked with `WAITING APPROVAL` status. +![Create Claim Fund - partner approval](../images/claim-fund-partner-approval1.png) + +* You can approve the transaction directly from this screen by clicking Approve button, or go to detail transaction to see more information before approve. +![Create Claim Fund - partner approval](../images/claim-fund-partner-approval2.png) + +* Click approve button to release the transaction to user. + +* The transaction is now marked as `IN PROGRESS` + +* Your recipient should get the money delivered to their account immediately. + +* In parallel, your customer will also get email notification about successful claim fund transaction. +![Create Claim Fund - user success](../images/claim-fund-user-success.png) + +## API Biller + +API biller product provides the capability for you to pay the bill products. With 130+ types of billing products, you can provide numerous bill payment options with ease and in real-time. +The integration process to use the API biller product is straight forward and the details can be checked [here](https://api-docs.oyindonesia.com/#biller-api). + +### Transaction Flow + +![Transacation Flow](../images/Flow_API_Biller.png) + +### Key Features + +**Overbooking** +OY! can use the funds directly from your Mandiri or CIMB bank accounts for your bill payment needs. Please contact our [business representative](partner@oyindonesia.com) for further details about this feature. + +**Check Transaction Status and Callback** + +For all bill inquiry & bill payment executed, you will receive notifications regarding your transaction whether it is successful, failed or pending. We also provide an API for you to check the transaction status manually. IP proxy is also available upon request to enhance the security and integrity of the callback you will receive. + +**Check Balance** + +You can check your available balance at anytime to ensure that you have sufficient balance to execute a bill payment. + +### Registration and Set Up +**Prerequisites** + +* Register an account on the [OY! dashboard](https://business.oyindonesia.com/register?) + +* Activate your account through the activation link sent via email + +* Upgrade your account + +* Upgrade request is approved + +* Provide IP to be whitelisted and callback link to our business team + +* Receive an API Key from us + +* Integrate with our [API](https://api-docs.oyindonesia.com/#biller-api) + +### Testing + +Once you successfully create an OY! account, you can immediately simulate bill payment via API. +Follow the below steps to test the flow: + +1. Create an account +2. Login into the dashboard +3. Change the environment to “demo” +4. Once the environment changed to demo, there will be API key demo available on the bottom left corner of the page +5. Before creating a bill payment transaction, check your available balance through API GET _https://api-stg.oyindonesia.com/api/balance_ +6. Request inquiry for the bill you want to pay by sending a ‘POST’ request to _https://api-stg.oyindonesia.com/api/v2/bill_ using your staging API key. Enter the required and optional fields, as referenced in the API reference docs (https://api-docs.oyindonesia.com/#bill-inquiry-biller-api) +7. Fill in the customer-id, product-id, and the partner transaction-id. You will get the detail information about the bill that you want to pay. +8. After successful inquiry, you should do the payment process by sending a ‘POST’ request to _https://api-stg.oyindonesia.com/api/v2/bill/payment_. Enter the required and optional fields, as referenced in the API reference docs (https://api-docs.oyindonesia.com/#pay-bill-biller-api) +8. To get the status of a bill payment request, you can call the API https://api-stg.oyindonesia.com/api/v2/bill/status +9. If payment is successful or failed, we will send a callback to the registered staging callback URL destination. Callback URL can be registered via our business representative. +10. The API biller transactions can be monitored through OY! dashboard from the “Send money - API biller" menu. + + +### How to Use + +In order to create API biller transaction, a sufficient available OY! balance is required in the account. More details and instructions about topping up to your OY! account can you see here https://docs.oyindonesia.com/#top-up-oy-dashboard-tutorial. + +Before you execute the bill payment, you have to verify the bill information from our [bill inquiry endpoint](https://api-docs.oyindonesia.com/#bill-inquiry-biller-api). + +> Below is an example of the request body for inquiry: + +```shell +curl -X POST https://partner.oyindonesia.com/api/v2/bill +-H 'content-type: application/json, accept: application/json, x-oy-username:myuser, x-api-key:987654' +-d '{ + "customer_id": "512233308943", + "product_id": "plnpost", + "partner_tx_id": "Tx15048563JKFJ" + }' +``` + +> It will return an [error message](https://api-docs.oyindonesia.com/#api-biller-response-codes-biller-api) if the request is not valid. Otherwise, below is the sample response parameters that will be returned: + +```json +{ + "status":{ + "code":"000", + "message":"Success" + }, + "data": { + "tx_id": "a3d87877-e579-4378-844b-c06294fc9564", + "partner_tx_id": "Tx15048563JKFJ", + "product_id": "plnpost", + "customer_id": "512233308943", + "customer_name": "Plg.,De'mo 512233308943", + "amount": 282380, + "additional_data": "{\"customer_id\":\"512233308943\",\"customer_name\":\"Plg.,De'mo 512233308943\",\"admin_fee\":\"2.500\"}" + } +} +``` + +> + +Next, send a request body to execute a bill payment request to be sent to our [bill payment endpoint](https://api-docs.oyindonesia.com/#pay-bill-biller-api). + +> Below is an example of the request body for the bill payment: + +```shell +curl -X POST https://partner.oyindonesia.com/api/v2/bill/payment +-H 'content-type: application/json, accept: application/json, x-oy-username:myuser, x-api-key:7654321' +-d '{ + "partner_trx_id":"Tx15048563JKFJ", + "note" :"biller transaction test" + }' +``` + +> Below is the sample response parameters that will be returned: + +```json +{ + "status":{ + "code": "102", + "message": "Request is In progress" + }, + "data": { + "tx_id": "a3d87877-e579-4378-844b-c06294fc9564", + "partner_tx_id": "Tx15048563JKFJ", + "product_id": "plnpost", + "customer_id": "512233308943", + "customer_name": "Plg.,De'mo 512233308943", + "amount": 282380, + "note": "biller transaction test" + }, +} +``` + +> + +An endpoint to [check the transaction](https://api-docs.oyindonesia.com/#get-bill-payment-status-biller-api) is also available and can be accessed at anytime. + +> Below is an example of the request body: + +```shell +curl -X POST https://partner.oyindonesia.com/api/b2/bill/status +-H 'content-type: application/json, accept: application/json, x-oy-username:myuser, x-api-key:7654321' +-d '{ + "partner_trx_id": "Tx15048563JKFJ" + }' +``` + +> The above command returns a JSON structured similar like this: + +```json +{ + "status":{ + "code": "000", + "message": "Success" + }, + "data": { + "tx_id": "a3d87877-e579-4378-844b-c06294fc9564", + "partner_tx_id": "Tx15048563JKFJ", + "product_id": "plnpost", + "customer_id": "512233308943", + "customer_name": "Plg.,De'mo 512233308943", + "amount": 282380, + "additional_data": "\"{\\\"bill_period\\\":\\\"FEB2022\\\",\\\"total_amount\\\":\\\"282.380\\\",\\\"customer_id\\\":\\\"512233308943\\\",\\\"customer_name\\\":\\\"Plg.,De'mo 512233308943\\\",\\\"admin_fee\\\":\\\"2.500\\\",\\\"settlement_date\\\":\\\"09/03/2022 16:49\\\"}\"", + "status": "SUCCESS" + }, +} +``` + +> + +A callback with the following information will be sent to the callback endpoint that you can register with us. + +You can also [check your balance](https://api-docs.oyindonesia.com/#get-balance) anytime to ensure you have sufficient balance from our endpoint. + +> Below is an example of a request body to check the balance: + +```shell +curl -X GET 'https://partner.oyindonesia.com/api/balance' +-H 'Content-Type: application/json' +-H 'Accept: application/json' +-H 'X-OY-Username: janedoe' +-H 'X-Api-Key: 7654321' +``` + +> Below is the sample response parameters that will be returned: + +```json +{ + "status":{ + "code":"000", + "message":"Success" + }, + "balance":100000000.0000, + "pendingBalance":2000000.0000, + "availableBalance":98500000.0000, + "overbookingBalance":98500000.0000, + "timestamp":"10-12-2019 12:15:37" +} +``` + +> + +Lastly, all transactions can be monitored from the OY! dashboard which includes all the transaction details. + +![API Biller](../images/API_Biller.png) + + +For further details on the parameters definition and proper usage, please refer to our [API Documentation](https://api-docs.oyindonesia.com/#biller-api). + +## Feature: Resend Callback + +### Key Features + +Retry Callback allows you to resend a callback of your transaction to your system. Initially, OY! will send a callback to your system after your transaction status has been updated. If your system failed to receive the callback, this feature can help you to retry the callback process. The process can be done in two ways + + +1. Automated retry callback +If the callback is not successfully received on the first try, the system will automatically retry the callback delivery. If that callback is still not received by the client's system, the system will automatically retry until 5 occurrences. The interval of the sending process will be detailed in the Callback Interval section. If all automated Retry Callbacks have been sent but still returned failed, the system will send an email notification to the email address set in the configuration. + +2. Manual retry callback +Besides the automated process, you can manually request a callback via the dashboard. + +### Registration and Set Up + +Follow the instruction below to activate retry callback + +1. Login to your account in OY! Dashboard +2. Open “Settings” and choose “Developer Option”. Choose “Callback Configuration” +3. Fill your callback URL in the related product that you want to activate. Make sure the format is right. You can click URL String Validation button to validate the URL format. +4. If you want to activate automated retry callback, check the Enable Automatic Retry Callback and fill in the email. The email will be used to receive a notification if all the automatic callback attempts have been made but still fail +5. Click "Save Changes". The configuration will not able to be saved if the callback URL or/and email format are not valid. + + +![Resend Callback](images/api_disburse_retry_callback_developer_option.png) + +Don't forget to whitelist these IPs in order to be able to receive callback from OY: 54.151.191.85 and 54.179.86.72 + +If you want to manually resend a callback, you can follow the instruction below + +1. Login to your account in OY! Dashboard +2. Open the API Disbursement menu +3. Click the "Resend Callback" button in the related transaction + + +![Resend Callback](images/api_disburse_resend_callback.png) + + + +### Callback Interval +1st retry: realtime (after the first failed log received) +2nd retry: 1 min (after callback failed log for the 1st retry is received) +3rd retry: 2 mins (after callback failed log for the 2nd retry is received) +4th retry: 13 mins (after callback failed log for the 3rd retry is received) +5th retry: 47 mins (after callback failed log for the 4th retry is received) + +If all automated Retry Callback (all the 5 attempts) has been sent but we still get a Failed response from your end, our system will send an automated email notification to the email address that has been set in the configuration earlier + + +### Idempotency Key +To implement automated retry callback, you need to handle the idempotency logic in your system using the below key: + +API Disburse: trx_id + +## Account Payable + +OY! Account Payable allows you to pay your vendor’s billings. This feature offers record transactions, creates approval layers, and schedules payment for invoice payables without hassle. Account Payable is made through the OY! dashboard, so no technical integration is required to use this product. + +### Key Features + +Parameter | Description +------ | ------ +Multi-layer Approval | By default, OY!’s account payable can be created and approved by the super admin or admin. However, you can configure additional approval layers to enhance security. +OCR enabled | OY! provides you with OCR for automatic input of payable invoices. You only need to upload the invoice then let OY!’s OCR do the job. +International Transfer | You can make overseas payments with OY!'s account payable. There's no need to visit the bank or convert your IDR money first—handle it anytime, anywhere with OY!'s account payable. + +### Registration and Set Up + +Follow the below check-list to ensure you're all set up to use the service: + +1. Create an account for OY! business +1. Verify your account by submitting the required documentations +1. Have your upgrade request approved +1. Set up your receiving bank account information (note: ensure that the receiving bank account information is accurate as it cannot be changed via OY! dashboard for security reasons) +1. Once your account is approved, you can start using Account Payable product + +### Testing + +#### Account Payable in IDR + +1. Log on to your OY! dashboard +1. Choose "Demo" environment +1. Create vendor data first by following the steps in How to Manage Vendor Data +1. After create the vendor data, you can start create Account Payable, please follow steps in How to Use Account Payable for IDR +1. You’ll see your created transaction in Account Payable table + +#### Account Payable for International Payment + +1. Log on to your OY! dashboard +1. Choose "Demo" environment +1. Create vendor data first by following the steps in How to Manage Vendor Data +1. After create the vendor data, you can start create account payable, please follow steps in How to Use Account Payable for International Payment +1. You’ll see your created transaction in Account Payable table + + +### How to Use Account Payable via Dashboard + +You can create new invoice to be paid and set up payment by following this step: + +1. Log on to your OY! dashboard +2. Choose "Production" environment +3. Click "Pay Invoice" under Account Payable menu +4. Click "Invoice List" +5. Click "Create New Invoice" on top right +6. Choose “Send IDR” +7. Upload your invoice document to help you easier record the invoice by click "Browse to Upload" or Drag & drop to the invoice area +8. Fill the payable invoice data. You can choose between these 2 options for input data: + 1. By Automatic OCR + 1. Click the toggle auto-fill on top left corner page + 2. Choose vendor that this invoice belongs to + 4. Click “Process” + 5. Your invoice will have status “Auto-Filling” until the OCR has successfully completed the extraction process + 2. Manual Input necessary details of payable invoice + + Parameter | Description + ------ | ----------- + Purchase Type | You can choose between purchase order, service fee, bill, subscription fee, and reimbursement. + Invoice Number | The number of the invoice that you get from your vendor/supplier. + Invoice Date | The date of the invoice. + Due Date | Due date of a transaction as mentioned in the invoice. Your approver will be reminded to approve on D-7, D-3, and D-1 from the invoice due date. + PO/PR Number (optional) | The reference PO/PR number from your company to track this invoice. + Note | The note for this invoice. + Vendor | The name of the vendor that this invoice belongs to. You can choose the name of the vendor from the dropdown menu. To create a new vendor, follow the instructions [here](https://docs.oyindonesia.com/#how-to-create-edit-and-inactivate-vendor-data-account-payable). + Product Description | The name and/or description of the product. + Quantity | The quantity of the product. + Price | Unit price of the product. + Total | Total price of the product (Total = Quantity x Price). + Subtotal | The total price of all the products + PPn | PPn that should be paid to the vendor. PPn is calculated from subtotal amount. You can set up the tax during vendor addition or edit in the "Vendor Management" menu under Account Payable. + PPh | PPh that should be deducted from the vendor. PPh is calculated from subtotal amount.You can set up the tax during vendor addition or edit in "Vendor Management" menu under Account Payable. + Total Pay to Vendor | Total amount that will be paid to vendor on scheduled date, post approval. + Reference Documents (Upload document) | The supporting documents that you want to record related to this invoice. Accept PDF files only. Up to 7 documents, each with a maximum file size of 2.5MB. + + Note: Maximum 20 rows for line item detail +9. If you’re using OCR, these following steps will be taken before setting your payment. + 1. If your invoice has been successfully processed by the OCR, then the status will be “Unconfirmed”. Click the invoice data to proceed the invoice + 2. Check whether the data is correct and choose the “Purchase Type” then click “Next” + +10. Continue to set up '”Invoice Payment Details”. You can set up the payment to one time payment (i.e. lump sum) by choosing “Full Payment” or multiple times payment by choosing “Partial Payment”. + + Parameter | Description + ------ | ----------- + Payment Amount | Amount that will be automatically paid to vendor on scheduled date + Due Date | The due date of the payment. The due date should be from today onward and not exceed the previously entered due date from the invoice. Reminder notification will be sent for approval D-7, D-3, and D-1 if the status is waiting approval. + Status | Status of the invoice payment. You can choose “Paid” to record the invoice as documentation only, meaning no payment will be processed by the system. For invoices that require payment, choose “Unpaid”. + Scheduled Payment | Time of the payment based on your preference. Please note that every payment will only be executed after approval and when your OY! balance is sufficient. + Remaining Amount | Total pay to vendor - subtotal. This amount should be 0 to continue the process. + +11. After Account Payable creation, you will be redirected to this table that shows all payable invoices you’ve made. Status: Waiting Payment, Partially Paid, Complete and Canceled. Below are the list of statuses you will find on “Invoice List”. + +Parameter | Description +------ | ----------- +Auto-Filling | Created invoice using OCR is still in process. +Unconfirmed | Invoice created using the OCR system is finished and needs to be processed to schedule the payment. +Incomplete | Invoice has been canceled. +Unpaid | Waiting for approval or balance is not enough. +Partially Paid | Multi time payment or partially paid that is not finished yet. You can click the invoice number to find the partial payment details in the 'Payment Transaction' tab. +Paid | All payment of the invoice is complete. + +12. For managing your transaction, you can see the table below. This table will show you all transactions you should pay/have paid. Status: Waiting Approval, Insufficient Balance, Failed, Canceled and Paid. Below is the list of statuses you will find on “Invoice Transaction” page. This page intended to process your payable invoice transactions. + +Parameter | Description +------ | ----------- +Waiting Approval | Waiting for approval. +Insufficient Balance | The payable invoice is approved and scheduled for payment but the balance is insufficient when the system tries to pay the balance at the scheduled date. +Failed | The payable invoice is approved and ready to be paid but due to insufficient balance the payment is failed. +Canceled | Invoice has been canceled. +Paid | The payable invoice already paid either full payment or partial payment. + +**Note:** Please allow up to 1 business day for the OCR extraction process + +* Create IDR Account Payable page + +![Create IDR Account Payable](images/accountPayable/creation_manual_idr.png) + +* Create Account Payable with OCR page + +![Create Account Payable OCR](images/accountPayable/creation_ocr_idr.png) + +* Filled Payable Invoice with OCR page + +![Filled AP with OCR](images/accountPayable/autofilled_idr.png) + +* Payment Option page + +![Payment Option](images/accountPayable/payment_option.png) + +* List of Invoice Payable + +![Invoice List](images/accountPayable/invoice_list.png) + + +### How to Use Account Payable via Dashboard (for International Payment) + +1. Log on to your OY! dashboard + +2. Choose "Production" environment + +3. Click "Pay Invoice" under Account Payable menu + +4. Click "Invoice List" + +5. Click "Create New Invoice" on top right + +6. Choose “Send Other Currency” + +7. Choose the currency you want to send; currently OY! available for these currencies : + +a. SGD + +b. HKD + +c. CNH + +d. USD + +8. If you select USD as the currency, choose the destination country. Other currencies, such as HKD or CNH, can only be used in their origin country. For example, if you choose HKD, you can only make payments to Hong Kong; currently we accept these countries for international payment in USD : + +a. Singapore + +b. China + +c. Hong Kong + +9. Upload your invoice document to help you record the invoice easier by clicking "Browse to Upload" or Drag & drop to the invoice area +10. Fill in the necessary details. Please input the total payment amount between IDR 200.000 - IDR 35.000.000 and make sure your OY! balance is enough to process the transaction or our system will restrict users from proceeding. + +**Note:** Maximum 20 rows for line item detail + +Parameter | Description +------ | ----------- +Purchase Type | You can choose between purchase order, service fee, bill, subscription fee, and reimbursement. +Invoice Number | The number of the invoice that you get from your vendor/supplier. +Invoice Date | The date of the invoice. +Due Date | Due date of a transaction as mentioned in the invoice. Your approver will be reminded to approve on D-7, D-3, and D-1 from the invoice due date. +PO/PR Number (optional) | The reference PO/PR number from your company to track this invoice. +Note | The note for this invoice. +Vendor | The name of the vendor that this invoice belongs to. You can choose the name of the vendor from the dropdown menu. To create a new vendor, follow the instructions [here.](https://docs.oyindonesia.com/#how-to-create-edit-and-inactivate-vendor-data-account-payable) +Product Description | The name and/or description of the product. +Quantity | The quantity of the product. +Price | Unit price of the product. +Total | Total price of the product (Total = Quantity x Price). +Subtotal | The total price of all the products. +Total Pay to Vendor | Total amount that will be paid to vendor on scheduled date, post approval. +Reference Documents (Upload document) | The supporting documents that you want to record related to this invoice. Accept PDF files only. Maximum 7 documents (maximum 2.5MB each). + +11. Click “Next” to move to the next process + +12. We will show the calculation of exchange rate and fee to process the payment, also we will inform you about required documents to support this process. Please click “Next” to continue the process + +13. Choose saved sender data or create new sender data (please follow [this instruction for create new sender data](https://docs.oyindonesia.com/#how-to-add-sender-data-for-international-payment-account-payable)) then click “Next” + +14. Choose saved receipt data or create new receipt data (please follow [this instruction for create new receipt data](https://docs.oyindonesia.com/#how-to-add-receipt-data-for-international-payment-account-payable)) then click “Next” + +15. Input supporting information regarding “Source of Fund” and “Transfer Reason”, also attach supporting documents related to the transaction then click “Next” + +16. You’ll see the summary of transactions, about the sender, receipt, and amount of money that will be deducted to your OY! Balance. Make sure all the data is correct. + +17. After careful review of the transaction, you may click “Send” + +18. The money will arrive to your recipient’s bank account no later than 3 business days + +* Create Account Payable International Transaction page + +![Create AP International](images/accountPayable/creation_fx.png) + +* Supporting Document page + +![Supporting Doc](images/accountPayable/supporting_docs_fx.jpg) + +* Summary Transaction page + +![Summary](images/accountPayable/summary_fx.jpg) + +19. After Account Payable creation, you will be redirected to this table that shows all payable invoices you’ve made. Status: Waiting Payment, Partially Paid, Complete and Canceled. Below are the list of statuses you will find on “Invoice List”. + +Parameter | Description +------ | ----------- +Auto-Filling | Created invoice using OCR is still in process. +Unconfirmed | Invoice created using the OCR system is finished and needs to be processed to schedule the payment. +Incomplete | Invoice has been canceled. +Unpaid | Waiting for approval or balance is not enough. +Partially Paid | Multi time payment or partially paid that is not finished yet. You can click the invoice number to find the partial payment details in the 'Payment Transaction' tab. +Paid | All payment of the invoice is complete. + +20. For managing your transaction, you can see the table below. This table will show you all transactions you should pay/have paid. Status: Waiting Approval, Insufficient Balance, Failed, Canceled and Paid. Below is the list of statuses you will find on “Invoice Transaction” page. This page intended to process your payable invoice transactions. + +Parameter | Description +------ | ----------- +Waiting Approval | Waiting for approval. +Insufficient Balance | The payable invoice is approved and scheduled for payment but the balance is insufficient when the system tries to pay the balance at the scheduled date. +Failed | The payable invoice is approved and ready to be paid but due to insufficient balance the payment is failed. +Canceled | Invoice has been canceled. +Paid | The payable invoice already paid either full payment or partial payment. + +* List of Invoice Payable + +![Invoice List](images/accountPayable/invoice_list.png) + +### How to Manage Vendor Data + +**Add New Vendor for Account Payable in IDR :** + +1. Click “Vendor Data” under “Send Money” menu +2. Click “Add New Vendor” +3. Choose “Domestic Vendor” +4. Fill in the necessary details +5. Click “Add Vendor” after complete registration of new vendor + + +Parameter | Description +------ | ----------- +Vendor ID | Unique ID of the vendor from your company. This is not mandatory. +Vendor Name | The company/vendor name. Make sure the vendor name matches the vendor’s NPWP (if any) to help your company with the tax record. +Vendor Address | Vendor address to be recorded. This is not mandatory. +Bank Name | Recipient bank name. We already provide the list of bank names in the form of a dropdown menu that you can select according to your needs. +Account Number | Recipient bank account number. You can check the inquiry by clicking “Get Account Name” after filling the account number. +PIC Name | The PIC name of this vendor. +PIC Email | The PIC or recipient active email. Payment/transfer receipt will be sent automatically to this email after payment is completed. +PIC WhatsApp | The PIC WhatsApp number for your record. This is not mandatory. +PPh | PPh type from this vendor. Default of the setting is Not Subject to PPh. There are several types of PPh you can choose as needed. +Vendor NPWP | The vendor NPWP number record that can be used for company reference to generate “Faktur Pajak”. +NPWP Document | Vendor NPWP document to be record. Accept PDF and JPG format. Maximum 10 MB +PPn | PPn type of this vendor. Default of the setting is not Subject to PPn. There are several types of PPn you can choose (i.e. PPn Inclusive means that subtotal is included the tax, PPn Exclusive means that subtotal is exclude the subtotal and will be added according to subtotal value) +SKB Document | Vendor SKB Document to be recorded. Accept PDF and JPG format. Maximum 10 MB. + +**Note:** Each vendor only has 1 type of PPh setting and 1 type of PPn setting. There will be a PPh email sent on the 1st day of each month that contains all the PPh from your vendors in the previous month. This report can help companies with tax payment, reporting, and “Faktur Pajak” generation to your vendor. + +* Create New Vendor + +![Add Vendor 1](images/accountPayable/add_vendor_1.png) + +![Add Vendor 2](images/accountPayable/add_vendor_2.png) + +![Add Vendor 3](images/accountPayable/add_vendor_3.png) + +* List of Vendor + +![List of Vendor](images/accountPayable/vendor_list.jpg) + +* Vendor Details + +![Vendor Details](images/accountPayable/vendor_details.jpg) + +**Add New Vendor for Account Payable for International Payment :** + +1. Click “Vendor Data” under “Send Money” menu +2. Click “Add New Vendor” +3. Choose “International Vendor” +4. Fill in the necessary details +5. Click “Save” after complete registration of new vendor + +Parameter | Description +------ | ----------- +Vendor ID | Unique ID of the vendor from your company. This is not mandatory. +Vendor Name | The company/vendor name. Make sure the vendor name matches the vendor’s NPWP (if any) to help your company with the tax record. +Vendor Address | Vendor address to be recorded. This is not mandatory. +PIC Name | The PIC name of this vendor. +PIC Email | The PIC or recipient active email. Payment/transfer receipt will be sent automatically to this email after payment is completed. +PIC WhatsApp | The PIC WhatsApp number for your record. This is not mandatory. + +### How to Set Up Invoice Payable Approval + +You can set up multi level approval from OY's users. There will be 4 type of users: Super Admin, Admin, Approver, and Maker + +**Approval Layer Set Up** + +By the first time you create your account, your account will be assigned as Super Admin. You have the ability to create, edit, and delete sub-account. Sub-account itself can be utilized as Maker only, Approver only, or Admin account. Please follow steps [here](https://docs.oyindonesia.com/#user-management-oy-dashboard-tutorial) for user management in OY! dashboard. It is not necessary to create new sub-accounts in order to use Account Payable. The Super Admin and Admin roles allows you to directly create and approve Account Payable. If you want to create multi-layer approval please follow the steps in the Multi-Layer Approval section. + +**Multi-Layer Approval** + +By default admin is the one who approves the payable invoices but you can create a multi-layer approval to enhance security of your account payable transactions. You can assign up to 3 sub-accounts with role Admin or Approver also the limit amount of transaction by follow the steps below: + +1. Log in to your OY! Account +1. Open “Multi Approval” under “Settings” menu +1. You’ll see 3 levels of approval, the first level (default) minimum amount is IDR 0 you can choose to have multiple approvals by selecting approver level to “All Accounts” or “Choosen Accounts” +1. If you want to create another level approval, please fill the minimum amount and choose approver for this level +1. If you’re done, please click “Save Changes” + +* Multi Approval page + +![Multi Approval Page](images/accountPayable/multi_account_management.png) + +By setting the multi-layer approval, every transaction will not be transferred to vendor without approval from all approvers. + +Default approval: Super Admin, Admin, and Approval. + +### How to Add Sender Data for International Payment + +1. Log on to your OY! dashboard +1. Choose "Production" environment +1. Click "Pay Invoice" under Account Payable menu +1. Click "Invoice List" +1. Click "Create New Invoice" on top right +1. Choose “Send Other Currency” +1. Fill all required fields +1. Click “Next” to move to the next process +1. We will show the calculation of exchange rate and fee to proceed the payment, also we will inform you about required documents to support this process. Please click “Next” to continue the process +1. OY! will show you the current saved sender data and option for creating a new sender +1. If you decide to create a new sender, choose type of sender (Individual or Business) +1. Fill these fields then click “Save and Next”, this process will automatically save the sender data + +**Fields for Individual Sender** + +Parameter | Description +------ | ----------- +First Name | Sender’s first name (optional) +Middle Name | Sender’s middle name (optional) +Last Name | Sender’s last name +Other Name | Sender’s other name if any (Chinese name, religious name, etc) +Gender | Gender of the sender +Occupation | Sender’s current job title +Date of Birth | Sender’s date of birth +Country of Birth | Sender’s origin country of birth +Nationality | Sender’s current nationality +Identity Document | Identity document that is used to input identity number +Identity Number | Identity number from the inputted identity document +Phone Number | Sender’s active phone number +Email | Sender’s active email +Residential Status | Current residential status of sender in Indonesia +Address Line | Sender’s origin address +City | City from the address +State/Province | State/province from the address +Country | Country from the address +Postal Code | Postal code of address + +**Fields for Business Sender** + +Parameter | Description +------ | ----------- +Business Name | Name of business (e.g. PT Dompet Harapan Bangsa) +Business Registration Number | A unique identifier assigned to a business by a government authority when it is officially registered. (NIB, Nomor Induk Berusaha) +Date of Incorporation | Date when the business was launched or registered +Website | Company profile website of the business +Country of Incorporation | Country where the business is incorporated in +Operate in | Country where the business is operating +Phone Number | Business sender active phone number +Email | Business sender active email/PIC for the business sender active email +Address Line | Business sender’s origin address +City | City from the address +State/Province | State/province from the address +Country | Country from the address +Postal Code | Postal code from the address + +* Sender form page for individual + +![Sender Individual](images/accountPayable/sender_individual_creation_fx.png) + +* Sender form page for business + +![Sender Business](images/accountPayable/sender_business_creation_fx.png) + + +### How to Add Receipt Data for International Payment + +1. Log on to your OY! dashboard +1. Choose "Production" environment +1. Click "Pay Invoice" under Account Payable menu +1. Click "Invoice List" +1. Click "Create New Invoice" on top right +1. Choose “Send Other Currency” +1. Fill all required fields +1. Click “Next” to move to the next process +1. We will show the calculation of exchange rate and fee to proceed the payment, also we will inform you about required documents to support this process. Please click “Next” to continue the process +1. Choose saved sender data or create new sender data ([please follow this instruction](https://docs.oyindonesia.com/#how-to-add-sender-data-for-international-payment-account-payable)). +1. OY! will show you the current saved receipt data and option for creating a new receipt. +1. If you decided to create new receipt, click type of receipt (Individual or Business) +1. Fill these fields then click “Save and Next”, this process will automatically save the sender data + + +**Fields for Individual Receipt** + +Parameter | Description +------ | ----------- +First Name | Receipt’s first name (optional) +Middle Name | Receipt’s middle name (optional) +Last Name | Receipt’s last name +Other Name | Receipt’s other name if any (Chinese name, religious name, etc) +Gender | Gender of the receipt +Occupation | Receipt’s current job title +Date of Birth | Receipt’s date of birth +Country of Birth | Receipt’s origin country of birth +Nationality | Receipt’s current nationality +Identity Document | Identity document that is used to input identity number +Identity Number | Identity number from the inputted identity document +Phone Number | Receipt’s active phone number +Email | Receipt’s active email +Residential Status | Current residential status of receipt in Indonesia +Address Line | Receipt’s origin address +City | City from the address +State/Province | State/province from the address +Country | Country from the address +Postal Code | Postal code of address +Bank Name | Registered bank name +Bank Address | Registered bank address +SWIFT/BIC Code | SWIFT/BIC code of the bank +Account Holder Name | Account holder name from the registered account number +Account Number | Account number that will receive funds + + +**Fields for Business Receipt** + +Parameter | Description +------ | ----------- +Business Name | Name of business (e.g. PT Dompet Harapan Bangsa) +Business Registration Number | A unique identifier assigned to a business by a government authority when it is officially registered. (NIB, Nomor Induk Berusaha) +Date of Incorporation | Date when the business was launched or registered +Website | Company profile website of the business +Country of Incorporation | Country where the business is incorporated in +Operate in | Country where the business is operating +Phone Number | Business receipt active phone number +Email | Business receipt active email/PIC for the business receipt active email +Address Line | Business receipt’s origin address +City | City from the address +State/Province | State/province from the address +Country | Country from the address +Postal Code | Postal code from the address +Bank Name | Registered bank name +Bank Address | Registered bank address +SWIFT/BIC Code | SWIFT/BIC code of the bank +Account Holder Name | Account holder name from the registered account number +Account Number | Account number that will receive funds + +* Receipt form page for individual + +![Receipt Individual](images/accountPayable/receipt_individual_creation_fx.png) + +* Receipt form page for business + +![Receipt Business](images/accountPayable/receipt_business_creation_fx.png) + + +## International Transfer + +International Transfer product provides the capability for users to transfer across countries from Indonesia at any time. You may create a transaction within OY! dashboard without the need for any technical integration. + +### Key Features + +**Account Management**
+When you first create an account, your account will be assigned as a Super Admin role. As a Super Admin, you have the ability to create new sub-accounts and and assign different roles to your team such as Admin, Maker and Approver that are applicable for international transfers. The Super Admin and Admin can also edit or delete created sub-accounts.\ +*Note: it is not necessary to create new sub-accounts in order to use international transfer. The Super Admin and Admin roles allows you to directly create and approve transactions.* + +**Roles & Accessibility**
+Only Super Admin and Admin roles are available to create transactions. However, if an approval flow is set up, Maker roles are allowed to create transaction with the approval of Super Admin, Admin or Approver roles. All other roles are only allowed to view transaction list, transaction details, filter, export and edit custom column in dashboard. + +**Available Destinations**
+Current list of countries supported: Singapore, Hong Kong and China.
+Current list of currencies supported: Singapore Dollars (SGD), Hong Kong Dollars (HKD), Chinese Yuan (CNY) and US Dollars (USD).
+More countries such as Australia, Malaysia, South Korea and United States will be available soon. + +**Sender / Recipient Contacts**
+Suppose that you want to create an international transfer on behalf of another entity, you may create an individual / business sender or recipient and input all required information about the sender or recipient. All sender and recipient information will be saved in contacts and may be reused in the future. + +**Additional Documents**
+We provide a placeholder for you to upload invoice and other supporting documents for the purpose of transfer and source of funds. + +**Transaction Details**
+Once a transaction is successfully created, all transaction details and updates will be recorded in OY! dashboard. + +**Single-layer Approval**
+Approval flow is designed to elevate your user experience and streamline the process of managing international transactions. The feature empowers our partners by allowing them to customize the approval process for transactions. Now, partners have the ability to assign Maker roles, responsible for initiating transaction creation, and Approver roles, ensuring each transaction undergoes a thorough review before execution. This not only adds an extra layer of security to your international remittances but also provides you with greater control over the entire process. + +### Registration and Set Up +**Prerequisites** + +* Register an account on the [OY! dashboard](https://business.oyindonesia.com/register?) + +* Activate your account through the activation link sent via email + +* Upgrade your account + +* Upgrade request is approved + +### Testing +1. Log in to your OY! dashboard +1. Choose "Staging" environment +1. Click "Send Money" menu, and choose "International Transfer" +1. Click "Create New Transaction" +1. Fill in the necessary details by following the steps explained in the “How to Use” section +1. Note: To reproduce a failed transaction in staging environment, you may fill the recipient account number as **1234567891**. All other account numbers will result in a successful transaction. + +### How to Use +In order to create international transfers, you need to have sufficient available OY! balance is required in the account. If there is an insufficient available balance in the account, international transfers cannot be created + * Create new transaction*: On the OY! dashboard, navigate to Send Payments > International Transfer on your left menu bar. Click “Create New Transaction” on the far righthand side of that page to create a new transfer. + +![Create New Transaction](images/internationalTransfer/create_inter_remit.jpg) + +* __Input transfer amount details__: You may fill out the amount of transfer in two ways: + 1. Fill in the send amount (in Rupiah) you would like to transfer, along with the destination currency and country. Our system will automatically convert according to the foreign exchange rate at that time. + 1. Fill in the recipient amount (in SGD/USD) you would like to transfer, along with the destination currency and country. Our system will automatically convert according to the foreign exchange rate at that time. + +![Input Send Amount](images/internationalTransfer/input_amount.jpg) + +*Note: If the nominal amount greater than the available balance, then our system will restrict users from proceeding* +![Balance Less Than Send Amount](images/internationalTransfer/balance_less_than_send_amount.jpg) + +* __Input sender details__: Decide whether sender is an individual or business entity, and you will see the corresponding details to fill out for each. Previously saved sender contacts will be displayed at the bottom of this page. +![List Of Existing Senders](images/internationalTransfer/list_of_existing_senders.jpg) + +Create a new individual sender by filling out this form + +![Input Individual Sender](images/internationalTransfer/input_individual_sender.jpg) + +Create a new business sender by filling out this form + +![Input Business Sender](images/internationalTransfer/input_business_sender.jpg) + +* __Input recipient details__: Decide whether recipient is an individual or business entity, and you will see the corresponding details to fill out for each. Previously saved recipient contacts will be displayed at the bottom of this page. + +![List Of Existing Recipients](images/internationalTransfer/list_of_existing_recipients.jpg) + +This will be the form you will need to fill out for individual recipient + +Create a new individual recipient by filling out this form + +![Input Individual Recipient](images/internationalTransfer/input_individual_recipient.jpg) + +Create a new business contact by filling out this form + +![Input Business Recipient](images/internationalTransfer/input_business_recipient.jpg) + +* __Add supporting information__: In this step, we need to record source of funds, purpose of transfer for the transaction. You may also attach supporting documents to aid the compliance requirements for your transaction. + +![Transfer Reason And Supporting Docs](images/internationalTransfer/transfer_reason_docs.jpg) + +* __Summary__: The summary of your transaction will be shown. If all the information is correct, you may click the **Submit** button at the bottom right hand corner of the screen. + +![Summary](images/internationalTransfer/summary.png) + +*Note: A fixed quotation rate is created since Input sender details and will be refreshed every 30 minutes. In the case when the quotation expires, a pop up will show to fetch the latest exchange rate and a new quotation is created.* + +![Update Exchange Rate](images/internationalTransfer/update_exchange_rate.png) + +If the available balance is insufficient for the new quotation amount, then you will be prompted to change the transfer amount or top up balance. + +![Balance Not Enough](images/internationalTransfer/balance_not_enough.png) + +* __Input Password__: For security reasons, OY! will prompt clients to input their password prior to every transaction. + +![Password Filled](images/internationalTransfer/password_filled.png) + +Each client has 5 chances to input the correct password. If you failed to input the correct password 5 times, then the transaction will automatically be cancelled. + +![Incorrect Password](images/internationalTransfer/incorrect_password.png) + +* __Transaction Status__: *In Progress, Success, Failed.* + +*In Progress* + +Once a transaction is successfully created, it will appear in your dashboard the status column will be set as *In Progress*. + +![In Progress Status](images/internationalTransfer/in_progress_transaction.png) + +*Success* + +Once your transaction is processed successfully, the status column will be updated as Success. Both sender and recipient should have received an email detailing a “Successful Transfer.” + +![Success Email](images/internationalTransfer/success_email.png) + +*Failed* + +If your transaction has failed to process, the status column will be updated as Failed. Both sender and recipient should have received an email detailing a “Failed Transfer.” + +![Failed Email](images/internationalTransfer/failed_email.png) + +* __Check transaction details__: You may check transaction details by clicking on the transaction id on the list of transaction details on dashboard + +![List Of Transactions](images/internationalTransfer/list_of_transactions.png) +![Transaction Detail](images/internationalTransfer/transaction_detail.png) + +### How to setup approval flow +1. Log in to your OY! dashboard. +1. Select the "Production" environment. +1. Navigate to "Multi Approval" product under the Settings menu. +1. Click on "Your International Transfer" tab. + + +By default the approval flow for international transfer is disabled. +![Activate approval](images/internationalTransfer/approval_default_disabled.png) + +When the approval flow is enabled, one approver is necessary to approve for all future transfers. You have the option to select specific username(s) or all usernames as approvers. +*Note: Only usernames with roles superadmin, admin and approver may approve future transactions* +![Approver selection](images/internationalTransfer/approver_selection.png) +![Account selected](images/internationalTransfer/account_selected.png) + +Since the initiation of the approval flow, every transaction must undergo approval before execution. +![One approver](images/internationalTransfer/one_approver.png) + +In the current state, the designated approver(s) will receive individual email notifications regarding pending approval transfers. +![Email approver](images/internationalTransfer/approver_email.png) + +Upon clicking the "Review the transaction" button, you will be redirected to the dashboard, where you can choose to either approve or reject the transaction. +*Note: Once a transaction is approved by any one approver, it will be executed immediately.* +![Approver decision](images/internationalTransfer/approver_decision.png) + + diff --git a/source/includes/id/_accepting-payments.md b/source/includes/id/_accepting-payments.md new file mode 100644 index 00000000000..f20b6274357 --- /dev/null +++ b/source/includes/id/_accepting-payments.md @@ -0,0 +1,1976 @@ +# Terima Uang +## Metode Pembayaran + +### Transfer Bank - Virtual Account +#### Pendahuluan +Sebagai metode pembayaran digital, Anda bisa membuat virtual account (VA) untuk transaksi Anda. Pelanggan dapat langsung mentransfer pembayaran ke nomor VA yang dibuat oleh sistem. Anda akan menerima notifikasi (callback) dari OY! saat transaksi sudah selesai. Untuk saat ini, OY! mendukung VA dari 8 bank: + +1. Bank Central Asia (BCA) +2. Bank Rakyat Indonesia (BRI) +3. Bank Mandiri +4. Bank Negara Indonesia (BNI) +5. Bank CIMB & CIMB Syariah +6. Bank SMBC +7. Bank Syariah Indonesia (BSI) +8. Bank Permata & Permata Syariah + +#### Ketersediaan Fitur VA +| Fitur VA | Link Pembayaran | VA Aggregator | E-Wallet Aggregator | Routing Pembayaran | +|:------------------|:----------------------------:|:-------------:|:-------------------:|:------------------:| +| Open Amount | ❌ | ✅ | ❌ | ❌ | +| Closed Amount | ✅ | ✅ | ❌ | ✅ | +| VA Pakai Berulang | ❌ | ✅ | ❌ | ✅ | +| VA Sekali Pakai | ✅ | ✅ | ❌ | ✅ | +| Statis | ❌ | ✅ | ❌ | ✅ | +| Dinamis | ✅ (otomatis diset ke 24 jam) | ✅ | ❌ | ✅ | +| VA Nomor Custom | ❌ | ✅ | ❌ | ❌ | + +Di samping parameter dari Link Pembayaran (seperti open amount, statis, dsb), pelanggan Anda harus memasukkan nominal yang harus dibayarkan sebelum memilih metode pembayaran dan membayar transaksi. + +Karena itu, jika pelanggan Anda memilih untuk membayar melalui VA - Link Pembayaran, nomor VA yang dihasilkan hanya dapat menerima jumlah yang telah ditentukan _(closed amount)_ dan hanya dapat digunakan untuk transaksi tersebut saja (_single-use_ VA). Nomor VA yang dihasilkan melalui Link Pembayaran akan kedaluwarsa dalam 24 jam setelah pelanggan Anda mengonfirmasi metode pembayaran. + +Pelanggan Anda juga bisa membuat nomor VA khusus. Anda bisa menyesuaikan suffix (nomor belakang) dari nomor VA berdasarkan nomor HP atau nomor tagihan pelanggan. Sebagai contoh, jika nomor HP pelanggan adalah 08123456789, maka ketika membuat VA, nomor VA nya akan menjadi 23088123456789. + +Untuk membuat VA yang bisa disesuaikan, Anda perlu mengakses endpoint URL yang berbeda dari endpoint pembuatan VA yang biasa, yaitu *[API Create Customized VA](https://api-docs.oyindonesia.com/#create-customized-va-va-aggregator)*. Endpoint API untuk [memperbarui](https://api-docs.oyindonesia.com/#update-customized-va-va-aggregator) dan [menonaktifkan](https://api-docs.oyindonesia.com/#deactivate-customized-va-va-aggregator) VA khusus juga berbeda dari VA yang tidak disesuaikan. Secara umum, VA khusus dapat menerima pembayaran berulang dan tidak ada waktu kedaluwarsa. Saat ini, fitur ini hanya tersedia untuk bank BRI dan CIMB. Untuk mengaktifkan fitur ini, silakan hubungi tim kami. + +#### Detail Nominal Transaksi +**Nominal minimum per transaksi** + +|Tipe Produk|Nominal minimum per transaksi | +| :-: | :-: | +|Link Pembayaran (Selain CIMB, Mandiri, Permata, BNI, BRI) |Rp 10,000 | +|Link Pembayaran (VA CIMB, Mandiri, Permata, BNI, BRI) |Rp 15,000 | +|Routing Pembayaran|Rp 10,000 | +|VA Aggregator (Closed Amount)|Rp 10,000 | +|VA Aggregator (Open Amount)|Tidak ada minimum | + +**Nominal maksimum per transaksi** + +|Nama Bank|Nominal maksimum per transaksi | +| :-: | :-: | +|Bank Central Asia (BCA) |Rp 50,000,000| +|Bank Negara Indonesia (BNI) |Rp 50,000,000| +|Bank Rakyat Indonesia (BRI) |Rp 500,000,000| +|Bank Mandiri |Rp 500,000,000| +|Bank CIMB |Rp 500,000,000| +|Bank SMBC |Rp 100,000,000| +|Bank Syariah Indonesia (BSI)|Rp 50,000,000| +|Bank Permata|Rp 500,000,000| + +#### Tersedia pada produk: + +|Link Pembayaran|VA Aggregator|E-Wallet Aggregator|Routing Pembayaran| +| :-: | :-: | :-: | :-: | +|✅|✅|❌|✅| + +#### Alur Pembayaran +![Bank Transfer Virtual Account Flow](images/acceptingPayments/payment-methods/bank-transfer-virtual-account/payment-flow.webp) +#### Cara Mengaktifkan +1. Bank selain BCA + + Tidak ada proses _onboarding_ tambahan untuk mengaktifkan metode pembayaran virtual account. Setelah produk Terima Uang aktif, Anda bisa menggunakan VA tanpa perlu dokumen tambahan. Anda cukup memberi tahu tim kami VA bank apa saja yang Anda perlukan. + +2. BCA + + Anda perlu mengajukan dokumen tambahan untuk mengaktifkan VA BCA(termasuk, namun tidak terbatas pada, Nomor Pokok Wajib Pajak (NPWP) dan KTP). Proses _onboarding_ bank BCA membutuhkan waktu kurang lebih 14 sampai 30 hari kerja, tergantung pada penilaian bank dan kelengkapan dokumen. + +#### Metode Pembayaran untuk Membayar VA + + +Pelanggan Anda dapat membayar tagihan VA melalui metode pembayaran berikut: + + +| Virtual Akun Bank | SKN | RTGS | ATMs | Mobile Banking antar bank | Internet Banking antar bank | Internet Banking (jaringan bank sama) | Mobile Banking (jaringan bank sama) | +|---------------------------------|:---:|:----:|:----:|:-------------------------:|:---------------------------:|:-------------------------------------:|:-----------------------------------:| +| Bank Mandiri | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | +| BRI | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ | +| BNI | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ | +| Permata | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ | +| CIMB Niaga / CIMB Niaga Syariah | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ | ✅ | +| BCA | ❌ | ❌ | ✅ | ✅ | ✅ | ❌ | ❌ | +| SMBC | ✅ | ❌ | ✅ | ✅ | ❌ | ❌ | ✅ | +| BSI | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | ✅ | + + + +#### Simulasi *Callback:* +1. Sebelum melakukan simulasi callback, perlu diingat bahwa Anda perlu membuat transaksi pada produk Terima Uang terlebih dahulu untuk mendapatkan nomor VA di mode Demo. +2. Untuk mensimulasikan pembayaran yang berhasil, pastikan Anda sudah berada pada mode Demo. Untuk masuk mode Demo, klik tombol “Coba di Demo” pada dashboard Anda. +3. Klik menu “Pengaturan”, lalu pilih pengaturan untuk “Callback Transfer Bank” +4. Pilih “Virtual Account” sebagai tipe transaksi +5. Pilih nama bank berdasarkan nomor VA yang telah Anda buat +6. Masukkan nomor VA dan nominal pembayaran. Untuk VA tipe closed, Anda perlu memasukkan nominal yang Anda tentukan saat membuat VA tersebut +7. Masukkan waktu pembayaran. Pastikan waktu pembayaran melebihi waktu pembuatan serta kurang dari waktu kedaluwarsa +8. Anda dapat menggunakan fitur ini untuk semua transaksi VA pada semua produk “Terima Uang” OY!, (Aggregator VA, Link Pembayaran, Routing Pembayaran) + + +![Bank Transfer - Virtual Account Simulate Payment](images/acceptingPayments/payment-methods/bank-transfer-virtual-account/simulating-callback.webp) + + +### Transfer Bank - Kode Unik +#### Pendahuluan +Kode Unik adalah tipe pembayaran melalui transfer bank dengan menambahkan atau mengurangi nominal berdasarkan kode unik (sebesar Rp 1-999) pada total nominal pembayaran Anda. Tidak seperti virtual account, di mana tiap pelanggan mendapatkan nomor VA yang berbeda, kode unik selalu menggunakan nomor rekening yang sama setiap bertransaksi. Nomor rekening yang digunakan akan menggunakan nama perusahaan OY! (PT. Dompet Harapan Bangsa), dan Anda tidak dapat menyesuaikan dengan rekening Anda sendiri. Metode kode unik juga mempunyai jam operasional, yaitu pada jam 03.00 - 20.30 WIB. + +Ada dua metode yang dapat Anda gunakan untuk transaksi kode unik: metode penambahan atau pendekatan pengurangan. + +1. Metode Penambahan + + Dengan menggunakan metode penambahan, nominal kode unik akan ditambahkan ke tagihan, sehingga pelanggan Anda akan membayar Rp 1-999 lebih banyak dari jumlah tagihan. Jumlah tambahan ini tidak akan masuk ke saldo Anda. + +2. Metode Pengurangan + + Dengan menggunakan metode pengurangan, jumlah tagihan akan dikurangi dengan nominal kode unik. Dalam hal ini, pelanggan Anda akan membayar Rp 1-999 lebih sedikit dari jumlah tagihan. Namun, jangan khawatir, jumlah yang masuk ke saldo Anda tidak akan terpengaruh oleh nominal kode unik ini. + +Metode default pada kode unik adalah penambahan, tetapi Anda dapat meminta perubahan metode dengan menghubungi tim kami. + +Sebagai contoh, jika Anda membuat transaksi kode unik dengan jumlah tagihan Rp 100.000, maka OY! akan membuat nominal kode unik untuk transaksi tersebut. Misalnya, nominal kode unik yang dihasilkan adalah Rp100. + +Jika Anda menggunakan metode penambahan, pelanggan Anda akan membayar total Rp100.100. + +Namun, jika Anda menggunakan metode pengurangan, pelanggan Anda akan membayar total Rp99.900. + +Dengan kedua metode tersebut, jumlah yang akan masuk ke saldo Anda tetap Rp100.000. + + +#### Detail Pembayaran Kode Unik + +|Bank|Kode Bank|Open Amount |Closed Amount |Waktu Pembayaran Maksimum|Jam Operasional| +| :-: | :-: | :-: | :-: | :-: | :-: | +|Bank Central Asia (BCA) |014|❌|✅|Max. 3 jam|03.00 - 20.30 WIB.| + +#### Fitur Pembayaran + +| Bank | Fitur Refund | +|:----:|:------------:| +| BCA | ❌ | + + +#### Ketersediaan Produk + +|Bank |Link Pembayaran|Aggregator VA|Aggregator E-Wallet|Routing Pembayaran| +| :- | :-: | :-: | :-: | :-: | +|BCA |✅|❌|❌|✅| + +#### Detail Nominal Transaksi + +|Metode Kode Unik|Min. Nominal Transaksi |Max. Nominal Transaksi| +| :-: | :-: | :-: | +|Pengurangan|Rp 11,000 |Rp 500,000,000| +|Penambahan|Rp 10,000 |Rp 499,999,000| + +#### Alur Transaksi Pembayaran +![Bank Transfer - Unique Code Flow](images/acceptingPayments/payment-methods/bank-transfer-unique-code/payment-flow.webp) +#### Aktivasi +Anda hanya dapat menggunakan satu jenis transfer bank (virtual account / kode unik) per bank. Secara default, semua bank menggunakan virtual account. Untuk menerima pembayaran menggunakan kode unik, Anda perlu mengajukan permintaan ke OY! melalui perwakilan bisnis Anda atau tim support kami. +#### Payment + +[Klik di sini untuk melihat tutorial pembayaran](https://drive.google.com/file/d/1D8cJEPFmVEN8-QVppiSm9RPa2vpb-b2H/view?usp=drive_link) + +#### Simulasi Pembayaran +Untuk memahami lebih lanjut tentang transaksi menggunakan kode unik, Anda dapat mensimulasikan transaksi kode unik yang dibuat di mode Demo. Berikut adalah langkah-langkah untuk mensimulasikan pembayaran kode unik melalui dashboard OY!: + +1. Buka dashboard OY! dan masuk ke mode Demo. +2. Buka menu “Pengaturan” dan klik Callback Transfer Bank. +3. Masukkan detail pembayaran untuk transaksi yang ingin Anda simulasikan: +- Jenis Transaksi: Pilih Kode Unik. +- Bank: Pilih bank tujuan. +- Nomor Rekening: Masukkan nomor rekening bank OY! Indonesia yang Anda terima saat pembuatan transaksi. +- Jumlah: Masukkan jumlah tagihan beserta nominal kode unik yang Anda terima saat pembuatan transaksi. +- Tanggal dan Waktu Pembayaran: Pilih tanggal dan waktu yang diinginkan untuk pembayaran. +4. Setelah semua kolom diisi, Anda dapat mensimulasikan pembayaran dengan mengklik “Kirim Callback”. Jika pembayaran berhasil, notifikasi sukses akan ditampilkan di dalam dashboard. OY! juga akan mengirimkan callback ke URL callback yang telah Anda tentukan. Jika karena alasan tertentu Anda tidak menerima callback, silakan hubungi layanan pelanggan kami untuk membantu menyelesaikan masalah tersebut. + +![Bank Transfer - Unique Code Simulate Payment](images/acceptingPayments/payment-methods/bank-transfer-unique-code/simulating-callback.webp) + +![Bank Transfer - Unique Code Amount Detail](images/acceptingPayments/payment-methods/bank-transfer-unique-code/payment-link-view.webp) + +### Kode QR (QRIS) +#### Pendahuluan +Quick Response Code Indonesian Standard (QRIS) adalah standar pembayaran QR di Indonesia yang dikembangkan oleh Bank Indonesia. Pembayaran dilakukan oleh pelanggan dengan memindai QR melalui aplikasi m-banking atau E-wallet mereka. Pembayaran QR sangat cocok untuk transaksi bernilai kecil karena menawarkan biaya yang terjangkau (0,7% per transaksi). + +#### Fitur Pembayaran + +| Provider QRIS | Fitur Refund | +|:-------------:|:------------:| +| QRIS | ❌ | + +#### Ketersediaan Produk + +| Provider QRIS | Link Pembayaran | Aggregator VA | Aggregator E-Wallet | Routing Pembayaran | +|:-------------:|:---------------:|:-------------:|:-------------------:|:------------------:| +| QRIS | ✅ | ❌ | ❌ | ✅ | + +#### Detail Nominal Transaksi + +Jumlah maksimum per transaksi untuk QRIS adalah Rp10.000.000, sedangkan jumlah minimum per transaksi adalah Rp10.000, baik melalui Link Pembayaran maupun Routing Pembayaran. Jika Anda ingin menerima pembayaran di bawah Rp10.000, silakan hubungi [perwakilan bisnis kami](customerservice@oyindonesia.com). + +#### Aktivasi +Untuk menerima pembayaran menggunakan QRIS, Anda perlu mendaftarkan merchant Anda terlebih dahulu ke provider QRIS. Pendaftaran dapat dilakukan melalui dashboard OY! dengan membuka halaman “Layanan Kami” dan mengklik tab “Metode Pembayaran”. + +Berikut adalah persyaratan yang harus dipenuhi untuk mengajukan pendaftaran: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+

+

Persyaratan

+
QRIS
Kepemilikan Bisnis
KTP Direktur atau Pemilik BisnisNIK
Nama Lengkap
Tanggal Lahir
Pekerjaan
Jenis Kelamin
Provinsi
Kota
Kecamatan
Kel/Desa
Kode Pos
Alamat Lengkap
Email Direktur
Nomor HP Direktur
NPWPNomor NPWP
Lampiran Dokumen
Person in Charge (PIC) - DirekturNama Lengkap
+

Jabatan

+

+

+
Person in Charge (PIC) - Non DirekturNama Lengkap
+

Jabatan

+

+

+
Alamat Email
Nomor HP
Alur Pembayaran Bisnis
Logo Bisnis (dalam format URL Gdrive))
Link Website
Nomor Induk Berusaha (sesuai dengan dokumen legalitas)
Tanggal Pendirian Usaha (sesuai dengan dokumen legalitas)
Tempat Pendirian Usaha (sesuai dengan dokumen legalitas)
Perkiraan omzet per bulan menggunakan QRIS
Perkiraan jumlah transaksi per bulan menggunakan QRIS
+ + +#### Pembayaran +[Klik di sini untuk melihat tutorial pembayaran](https://drive.google.com/file/d/1nwoMKH8iKaq8S89an0_bPlNQ11xMyo7T/view?usp=sharing) + +#### Simulasi Pembayaran +Saat ini, demo simulasi pembayaran QRIS untuk transaksi belum tersedia. + +### E-Wallet +#### Pendahuluan +E-wallet adalah metode pembayaran elektronik yang memungkinkan Anda membayar barang dan jasa tanpa memerlukan rekening bank atau uang tunai. E-wallet memiliki peran penting dalam pertumbuhan e-commerce, karena memungkinkan pengguna melakukan pembayaran dengan mudah tanpa harus berinteraksi dengan bank atau pihak ketiga lainnya. Saat ini, OY! mendukung pembayaran dari beberapa E-wallet di Indonesia, yaitu OVO, DANA, ShopeePay, dan LinkAja. + +#### Detail Pembayaran E-Wallet + +| Provider E-Wallet |Kode E-Wallet| Waktu Berlaku |Tipe Alur|Bayar via Desktop|Bayar via Browser HP| +|:-----------------:| :- |:--------------| :- | :-: | :-: | +| ShopeePay |shopeepay\_ewallet| 1 - 60 menit |Dialihkan ke app (JumpApp)|❌|❌| +| OVO |ovo\_ewallet| 55 detik |Notifkasi|❌|❌| +| DANA |dana\_ewallet| 1 - 60 menit |Dialihkan ke app (JumpApp)|✅|✅| +| LinkAja |linkaja\_ewallet| 5 menit |Dialihkan ke app (JumpApp)|❌|❌| + +Jumlah maksimum per transaksi untuk semua provider E-wallet adalah Rp10.000.000 untuk pelanggan yang telah melakukan KYC di aplikasi provider, dan Rp2.000.000 untuk pelanggan yang belum melakukan KYC. + +#### Fitur Pembayaran + +| Provider E-Wallet | Fitur Refund | Fitur Account Linking | Waktu Kedaluwarsa Link | Perpanjangan Link | +|:-----------------:|:---------------:|:---------------------:|:----------------------:|:-------------------:| +| ShopeePay | Refund Penuh | ✅ | 5 tahun | Setelah kedaluwarsa | +| OVO | Belum Didukung | ❌ | - | - | +| DANA | Refund Sebagian | ✅ | 10 tahun | Setelah kedaluwarsa | +| LinkAja | Refund Penuh | ❌ | - | - | + +#### Ketersediaan Produk + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Provider E-WalletLink PembayaranAggregator VAAggregator E-Wallet Routing Pembayaran
Single Payment-Single PaymentSingle PaymentDirect Payment
ShopeePay
OVO
DANA
LinkAja
+ +#### Tipe Pembayaran Beserta Alurnya +##### Single Payment +Single Payment adalah tipe pembayaran untuk pelanggan Anda membayar transaksi langsung di aplikasi E-wallet mereka. Single Payment memiliki 2 alur pembayaran, yaitu dialihkan ke app (JumpApp) dan notifikasi. Untuk mengerti alurnya, Anda dapat melihat bagan berikut: + +![Bagan alur JumpApp](images/acceptingPayments/payment-methods/e-wallet/payment-flow-single-payment-redirection.webp) + +![Bagan alur Notifikasi](images/acceptingPayments/payment-methods/e-wallet/payment-flow-single-payment-push-notification.webp) + +##### Direct Payment +Tipe pembayaran Direct Payment membutuhkan pelanggan Anda untuk melakukan _account linking_. _Account linking_ mengharuskan pelanggan Anda untuk menghubungkan akun E-walletnya ke sistem Anda sebelum melakukan pembayaran. Direct Payment memberikan kemudahan kepada pelanggan, karena pelanggan tidak perlu diarahkan ke aplikasi provider E-wallet untuk melakukan pembayaran. + +Dengan Direct Payment, pelanggan dapat melakukan transaksi dengan atau tanpa otorisasi (auto-debit). Transaksi dengan otorisasi memerlukan PIN atau OTP, sedangkan transaksi tanpa otorisasi memungkinkan sistem memotong saldo secara otomatis tanpa PIN atau OTP, cocok untuk kebutuhan seperti langganan _(subscription)_. + +![E-wallet Direct Payment Flow](images/acceptingPayments/payment-methods/e-wallet/payment-flow-direct-payment.webp) + +#### Aktivasi +Untuk menerima pembayaran menggunakan E-wallet, Anda perlu mendaftarkan merchant Anda terlebih dahulu ke provider E-wallet. Pendaftaran dapat dilakukan melalui dashboard OY! dengan membuka halaman “Layanan Kami” dan mengklik tab “Metode Pembayaran”. OY! menyediakan pendaftaran secara _real-time_, sehingga Anda dapat langsung menerima pembayaran setelah selesai mengajukan pendaftaran. Berikut adalah persyaratan yang harus dipenuhi untuk mengajukan pendaftaran: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+

+

Persyaratan

+
Provider E-Wallet
ShopeePayLinkAjaDANAOVO
Kepemilikan Bisnis
KTP Direktur atau Pemilik BisnisKTP
Nama Lengkap
Tanggal Lahir
Pekerjaan
Jenis Kelamin
Provinsi
Kota
Kecamatan
Kel/Desa
Kode Pos
Alamat Lengkap
Email Direktur
Nomor HP Direktur
NPWPTipe NPWP
Nomor NPWP
Lampiran Dokumen
Provinsi
Kota
Kecamatan
Kel/Desa
Kode Pos
Alamat Lengkap
Person in Charge (PIC) - Non DirekturNama Lengkap
+

Jabatan

+

+

+
Alamat Email
Nomor HP
Apakah Izin Usaha Anda Mempunyai Masa Berlaku?
Alur Pembayaran Bisnis
Logo Bisnis (dalam format URL Gdrive)
Link Website
Nomor Induk Berusaha (sesuai dengan dokumen legalitas)
Nomor Induk Berusaha / Sertifikat Pendaftaran Perusahaan
Tanggal Pendirian Usaha (sesuai dengan dokumen legalitas)
Tempat Pendirian Usaha (sesuai dengan dokumen legalitas)
Perkiraan omzet per bulan menggunakan E-Wallet
Perkiraan jumlah transaksi per bulan menggunakan E-Wallet
+ + +#### Pembayaran +ShopeePay (Single Payment) + +[Klik di sini untuk melihat tutorial pembayaran](https://drive.google.com/file/d/162WY4oEKwEcvgF7p_1lQ6VLHS4XGS-yI/view?usp=drive_link) + +DANA + +[Klik di sini untuk melihat tutorial pembayaran](https://drive.google.com/file/d/1c65gLG1gZdGKWhM6FazlqRA5qJVRhOYf/view?usp=drive_link) + +LinkAja + +[Klik di sini untuk melihat tutorial pembayaran](https://drive.google.com/file/d/1bN4-fVS0i1ygK96UVo7XcdnIhhQqzZi3/view?usp=drive_link) + +OVO + +[Klik di sini untuk melihat tutorial pembayaran](https://drive.google.com/file/d/1b_ImuHTQPGF1UE6jrtxhqRAShpbx8DDi/view?usp=sharing) + +#### Simulasi Pembayaran +Untuk memahami lebih lanjut tentang transaksi menggunakan E-wallet, Anda dapat mensimulasikan transaksi E-wallet yang dibuat di mode Demo. Berikut adalah langkah-langkah untuk mensimulasikan pembayaran E-wallet melalui dashboard OY!: + +1. Buka dashboard OY! dan masuk ke mode Demo. +2. Buka menu “Pengaturan” dan klik Callback E-wallet. +3. Masukkan detail pembayaran untuk transaksi yang ingin Anda simulasikan: + 1. Pilih provider E-wallet: ShopeePay, DANA, OVO, atau LinkAja + 2. Masukkan nomor referensi transaksi. Untuk transaksi E-wallet yang dibuat via API E-wallet, Anda bisa menemukan nomor referensi pada “Menu API E-wallet”. Untuk lebih jelasnya, silakan lihat gambar di bawah. + 3. Masukkan nominal transaksi yang ingin dibayar +4. Setelah semua kolom diisi, Anda dapat mensimulasikan pembayaran dengan mengklik “Kirim Callback”. Jika pembayaran berhasil, notifikasi sukses akan ditampilkan di dalam dashboard. OY! juga akan mengirimkan callback ke URL callback yang telah Anda tentukan. Jika karena alasan tertentu Anda tidak menerima callback, silakan hubungi layanan pelanggan kami untuk membantu menyelesaikan masalah tersebut. + +![E-wallet Simulate Callback](images/acceptingPayments/payment-methods/e-wallet/simulating-callback.webp) +![E-wallet See Reference Number](images/acceptingPayments/payment-methods/e-wallet/preview-dashboard.webp) + +Khusus untuk transaksi Link Pembayaran, Anda dapat mensimulasikan pembayaran dengan klik tombol “Bayar Tagihan” pada Link Pembayaran yang telah Anda buat. + +![E-wallet Simulate Callback Payment Link](images/acceptingPayments/payment-methods/e-wallet/simulating-callback.webp) + +### Kartu Debit dan Kredit +#### Pendahuluan +OY! menyediakan metode pembayaran melalui kartu debit dan kredit untuk pelanggan Anda. Untuk saat ini, OY! mendukung kartu VISA dan Mastercard. + +#### Detail Fitur +1. Transaksi aman dengan perlindungan 3DSecure – memberikan keamanan bagi Anda dan pelanggan Anda dari risiko penipuan. +2. Mendukung berbagai jaringan global (Visa dan Mastercard) – mempermudah pemrosesan transaksi lokal dan internasional. + +Untuk memastikan transaksi berjalan dengan lancar, pastikan pelanggan Anda telah: +1. Menyalakan 3DS sebagai metode otentikasi untuk setiap transaksi yang dilakukan dari kartu mereka +2. Memiliki saldo atau limit kredit yang cukup untuk tiap transaksi +3. Khusus untuk transaksi internasional, pastikan bank atau perusahaan penyedia kartu telah diizinkan untuk melakukan transaksi internasional + +Nominal minimum untuk pembayaran kartu adalah Rp15.000 dan tidak ada nominal maximum untuk tiap transaksi. Sementara, limit kartu akan ditentukan oleh saldo atau limit kredit yang dimiliki tiap kartu. + +#### Penjelasan untuk Transaksi Internasional + +Pelanggan Anda dapat menggunakan kartu debit atau kredit yang diterbitkan di dalam atau pun luar negeri. Jika Anda berencana melakukan transaksi internasional, perlu diingat bahwa OY! hanya bisa melakukan transaksi dalam Rupiah. Artinya, pelanggan Anda tetap dapat menggunakan kartu internasional mereka untuk pembayaran, namun kartu tersebut akan tetap dikenakan biaya dalam Rupiah, dan penyelesaian dana ke saldo OY! akan dilakukan dalam Rupiah. Sementara, pada laporan tagihan penyedia kartu akan ditampilkan mata uang lokal mereka, beserta rate kurs dan biaya tambahan (jika ada) yang dikenakan oleh penyedia kartu. + +Beberapa penyedia kartu mungkin tidak mengizinkan untuk melakukan transaksi internasional. Karena itu, penting untuk pelanggan Anda untuk mengecek apakah ada limitasi untuk meminimalisir transaksi ditolak oleh penyedia kartu. + +#### Transactions Declined by Issuer + +Ketika transaksi diajukan ke penyedia kartu pelanggan Anda, biasanya mereka memiliki sistem otomatis dan parameter yang membantu mereka dalam memutuskan apakah akan mengotorisasi transaksi tersebut atau tidak. Parameter ini dapat mencakup, namun tidak terbatas pada, aktivitas dari transaksi-transaksi sebelumnya, detail kartu; seperti tanggal kedaluwarsa dan CVV, serta ketersediaan saldo. + +Meskipun semua detail kartu sudah benar, saldo kartu tersedia, dan 3DS sudah diaktifkan, masih ada kemungkinan transaksi ditolak oleh penyedia kartu. Terkadang, alasan penolakan yang diberikan terlalu “umum”. Jika itu terjadi, Anda dapat meminta pelanggan Anda untuk menggunakan kartu lain atau metode pembayaran yang lain. Selain itu, pelanggan dapat menghubungi penyedia kartu langsung untuk informasi lebih lanjut mengenai alasan yang lebih spesifik. Penyedia kartu kemungkinan besar tidak akan memberikan penjelasan penolakan melalui OY!. + +#### Alur Transaksi Pembayaran + +Alur via Link Pembayaran + +![Cards Flow via Payment Link](images/acceptingPayments/payment-methods/cards/payment-flow-via-payment-link.webp) + +Alur via Routing Pembayaran + +![Cards Flow via Payment Routing](images/acceptingPayments/payment-methods/cards/payment-flow-via-payment-routing.webp) + +#### Aktivasi + +Untuk menerima pembayaran menggunakan kartu debit atau kredit, Anda perlu menghubungi [perwakilan bisnis kami](customerservice@oyindonesia.com) untuk informasi lebih lanjut mengenai proses aktivasi. + +|Persyaratan |Kartu Debit atau Kredit| +| :-: | :-: | +|Formulir Kelayakan Partner|✅| +|Formulir Permintaan Partner ke Bank |✅| +|Perjanjian Kerja Sama dengan OY! |✅| +|Profil Perusahaan |✅| +|Izin Operasional (jika berlaku)|✅| +|Alur KYC |✅| +|Daftar User|✅| + + +#### Simulasi Pembayaran +1. Buka dashboard OY! dan masuk ke mode Demo +2. Buat transaksi Link Pembayaran. Pastikan Anda memilih “Kartu Debit/Kredit” sebagai opsi metode pembayaran +3. Buka link pembayaran melalui browser +4. Pilih “Kartu Debit/Kredit” sebagai metode pembayaran Anda. Selanjutnya, Anda akan diarahkan ke halaman untuk mengisi detail kartu. Jika tidak diarahkan secara otomatis, silakan klik tombol “Pembayaran dengan Kartu” +5. Isi detail kartu dengan informasi sebagai berikut: + - Nomor Kartu: 2223000000000007 + - Masa Aktif Kartu: 01/39 + - CVV Kartu: 100 + - Nama Pemegang Kartu: Testing +6. Isi email dan nomor HP +7. Klik Bayar +8. Anda akan diarahkan ke halaman untuk simulasi otentikasi 3DS. (pada pembayaran sebenarnya, Anda akan menerima OTP yang dikirimkan ke nomor HP Anda oleh bank). Pilih dari menu dropdown untuk transaksi yang berhasil diautentikasi (catatan: Anda juga dapat mensimulasikan transaksi yang ditolak dengan memilih opsi otentikasi gagal dari menu dropdown). +9. Transaksi sukses + +## Link Pembayaran +Link Pembayaran adalah halaman pembayaran yang membantu bisnis Anda untuk menerima pembayaran online dengan mudah dan aman. Anda dapat membagikan link ke pelanggan dan mereka akan memilih beberapa metode pembayaran yang didukung oleh OY! dalam halaman Link Pembayaran. OY! mendukung lebih dari 17 metode pembayaran, seperti transfer bank, E-wallet, QRIS, serta kartu debit dan kredit. Link Pembayaran dapat dibuat tanpa integrasi. Tetapi, jika Anda ingin membuat Link Pembayaran dari website atau aplikasi Anda, OY! juga menyediakan API Link Pembayaran. + +Terdapat 2 tipe Link Pembayaran, yaitu: + +|Tipe Link Pembayaran|Penjelasan|Contoh Penggunaan| +| :- | :- | :- | +|Sekali Pakai|Satu link untuk menerima satu kali pembayaran.|Transaksi sekali bayar| +|Pakai Berulang|Satu link untuk menerima pembayaran berulang, bisa diatur tanpa waktu kedaluwarsa.|Top up, Donasi| + +![Payment Link Preview](images/acceptingPayments/payment-link/flow/preview-payment-link.webp) + +### Alur Link Pembayaran + +![Payment Link One Time Flow](images/acceptingPayments/payment-link/flow/payment-flow-one-time.webp) +![Payment Link Reusable Flow](images/acceptingPayments/payment-link/flow/payment-flow-reusable.webp) + +### Fitur Link Pembayaran +#### Penyesuaian Konfigurasi Link Pembayaran +Anda dapat mengatur konfigurasi Link Pembayaran Anda tergantung dari penggunaan transaksi bisnis Anda. Berikut adalah beberapa hal yang dapat Anda sesuaikan: + +1. Daftar Metode Pembayaran + + Atur metode pembayaran yang Anda sediakan untuk pelanggan. Metode pembayaran yang tersedia adalah transfer bank (via virtual account dan kode unik), kartu debit dan kredit, E-wallet, dan QRIS + +2. Tipe Nominal + - Open Amount: Dapat menerima pembayaran dengan maksimal nominal yang telah Anda tentukan saat membuat Link Pembayaran. + - Closed Amount: Hanya menerima pembayaran dengan nominal yang telah Anda tentukan saat membuat Link Pembayaran. + +3. Tipe Biaya Admin + - Termasuk dalam nominal: Biaya admin akan terpotong saat saldo sudah masuk ke rekening Anda. Dengan kata lain, biaya admin tersebut ditanggung oleh Anda (merchant). + - Tidak termasuk dalam nominal: Biaya admin akan ditambahkan ke dalam nominal yang akan dibayar (biaya admin ditanggung) oleh pelanggan Anda. Jadi, nominal yang ditagih ke pelanggan Anda adalah: Nominal pembayaran + biaya admin. + +4. Masa Kedaluwarsa + - Secara default, masa kedaluwarsa link pembayaran adalah 24 jam. Tetapi, Anda dapat menyesuaikan masa kedaluwarsa tersebut berdasarkan hari dan jam, dengan maksimum waktu pembayaran adalah 31 hari + 23 jam. + - Khusus untuk Link Pembayaran berulang, Anda dapat mengatur waktu kedaluwarsa menjadi tanpa kedaluwarsa, yang artinya link tersebut tidak dapat kedaluwarsa, kecuali Anda menonaktifkannya secara manual. + +Anda dapat mengatur konfigurasi default Anda agar tidak perlu mengisi kolom-kolom ini lagi saat membuat Link Pembayaran berikutnya. Simpan konfigurasi default Anda dengan mencentang opsi "Gunakan konfigurasi ini untuk transaksi selanjutnya" saat membuat Link Pembayaran. + +![Payment Link Save Configuration](images/acceptingPayments/payment-link/features/default-config.webp) + +#### Memantau Transaksi via Dashboard +Semua transaksi Link Pembayaran yang Anda buat akan ditampilkan di Dashboard OY!. Akses menu "Link Pembayaran" → "Sekali Pakai"/"Pakai Berulang" untuk melihat daftar transaksi yang telah dibuat. Di dalam dashboard, Anda dapat melihat detail transaksi, termasuk informasi yang dimasukkan saat pembuatan, status transaksi, dan nomor referensi pembayaran*. Dashboard juga dilengkapi fitur pencarian, penyaringan, dan ekspor daftar transaksi ke berbagai format: Excel (.xlsx), PDF (.pdf), dan CSV (.csv). + +![Payment Link Monitoring Transactions](images/acceptingPayments/payment-link/features/dashboard-view.webp) + +\*Nomor Referensi Pembayaran adalah nomor identitas pembayaran ketika pelanggan berhasil menyelesaikan pembayaran QRIS. Nomor referensi ini juga tercantum pada struk/bukti transaksi pelanggan. Fitur ini hanya tersedia untuk transaksi QRIS. + +#### Sesuaikan Tampilan + +Secara default, Link Pembayaran menggunakan tema bawaan OY!. Namun, tema bawaan ini mungkin tidak sesuai dengan branding perusahaan Anda. Untuk menjaga konsistensi brand bagi pelanggan Anda, Anda dapat menyesuaikan tampilan Link Pembayaran dengan melakukan hal-hal berikut: + +1. Unggah logo perusahaan atau bisnis Anda sendiri +2. Memilih warna tema +3. Memilih warna tombol + +Jika Anda menggunakan produk invoice OY!, penyesuaian tema Link Pembayaran juga akan diterapkan pada invoice, begitu pula sebaliknya. + +Anda dapat menyesuaikan tema untuk Link Pembayaran melalui Dashboard OY!. Berikut langkah-langkahnya: + +1. Masuk ke dashboard OY! melaui https://www.desktop-business.oyindonesia.com. Saat ini, menu penyesuaian hanya tersedia di versi web desktop (belum tersedia di web seluler dan aplikasi OY! Business). +2. Masuk ke menu “Pengaturan dan klik opsi “Tampilan Link Pembayaran”. +3. Untuk mengunggah logo perusahaan, Anda harus mengunggah dengan format (contoh: https://example.com/image.jpg). + - Jika Anda tidak memiliki URL logo, gunakan tools online seperti [snipboard.io](http://snipboard.io) atau [imgbb](https://imgbb.com/) untuk mengonversi logo Anda ke URL. + - Berikut adalah contoh URL yang benar: + - Snipboard.io: https://i.snipboard.io/image.jpg + - Imgbb: https://i.ibb.co/abcdef/image.jpg +4. Untuk mengunggah warna header, pilih warna menggunakan _Color Picker_ atau ketik kode warna HEX di kolom “Warna Header” (contoh: #FFFFFF). +5. Anda dapat memilih warna yang berbeda untuk tombol di dalam Link Pembayaran. Pilih warna dari _Color Picker_ atau ketik kode warna HEX di “Warna Tombol & Link”. +6. Terakhir, simpan perubahan. Perubahan akan diterapkan secara real-time pada semua Link Pembayaran yang dibuat. Anda juga dapat melihat waktu terakhir Link Pembayaran diperbarui. + +![Payment Link Customizing Theme](images/acceptingPayments/payment-link/features/dashboard-customized-payment-link.webp) + +Berikut adalah contoh Payment Link sebelum dan sesudah disesuaikan. Warna header dan tombol telah disesuaikan menjadi warna merah. + +![Payment Link Customized](images/acceptingPayments/payment-link/features/payment-link-after-changes.webp) + +#### Bagikan _Payment Link_ Melalui Berbagai Metode +Anda dapat membagikan _Payment Link_ yang telah dibuat langsung kepada pelanggan Anda melalui berbagai metode, termasuk email, pesan WhatsApp, dan salin tautan. + +***Email***
+Kirim Payment Link yang telah dibuat hingga ke 6 penerima email per Payment Link. Fitur ini tersedia untuk pembuatan melalui Dashboard dan API. Mengirimkan Payment Link melalui email tidak dikenakan biaya. + +Anda dapat mengisi daftar penerima email di kolom “Email” pada bagian “Detail Pelanggan” saat membuat Payment Link melalui Dashboard OY!. Pisahkan beberapa alamat email dengan tanda titik koma (;). + +Contoh: email1@company.com;email2@company.com;email3@company.com + +Jika Anda menggunakan API Link Pembayaran, Anda dapat memasukkan penerima email di bawah parameter “customer_email” saat membuat Payment Link. Untuk informasi lebih lanjut, silakan merujuk ke bagian Pembuatan Payment Link di [API Docs](https://api-docs.oyindonesia.com/#api-create-payment-link-fund-acceptance). + +***WhatsApp***
+Kirim Payment Link ke akun WhatsApp tanpa batas per Payment Link. Fitur ini hanya tersedia untuk pembuatan melalui API dan tidak didukung untuk pembuatan melalui Dashboard. Secara default, fitur ini tidak diaktifkan secara otomatis setelah pendaftaran. Anda dapat menghubungi perwakilan bisnis Anda untuk mengaktifkan fitur ini. + +Setelah berhasil membuat Payment Link, gunakan API Kirim WhatsApp untuk mengirim Payment Link yang telah dibuat ke pelanggan Anda. Untuk informasi lebih lanjut, silakan merujuk ke bagian [API Kirim WhatsApp](https://api-docs.oyindonesia.com/#api-send-whatsapp). + +Pelanggan Anda akan menerima pesan WhatsApp dari akun WhatsApp OY! dengan format berikut: + + +Hi {{Nama Pelanggan}},
+Anda memiliki transaksi di {{Nama Brand Anda}} yang sedang menunggu pembayaran. Lakukan pembayaran sebelum {{Waktu Kedaluwarsa Payment Link}}. +Silakan klik link berikut untuk membayar: {{URL Payment Link}} + +Mohon untuk tidak membalas pesan ini. + +Contoh: + +Hi John Doe,
+Anda memiliki transaksi di Jane’s Store yang sedang menunggu pembayaran. Lakukan pembayaran sebelum 1-Feb-2022, 13.28. +Silakan klik link berikut untuk membayar: + +Mohon untuk tidak membalas pesan ini. + +**Salin Link**
+Setelah Anda membuat Link Pembayaran, Anda akan mendapatkan link URL yang dapat Anda salin dan bagikan kepada pelanggan Anda. + +Jika Anda membuat Link Pembayaran melalui aplikasi OY! Business, Anda bisa menggunakan fitur “Bagikan” yang ada pada app dari perangkat mobile Anda ketika akan membagikan link. + +![Payment Link Sharing Capabilities](images/acceptingPayments/payment-link/features/copy-link.webp) + +#### Bukti Pembayaran +Pelanggan dapat langsung melihat bukti pembayaran di dalam Link Pembayaran setelah pembayaran berhasil. Selain itu, pelanggan juga dapat menerima bukti pembayaran melalui email yang Anda masukkan saat membuat Link Pembayaran. Anda dapat mengatur pengiriman bukti pembayaran melalui email kepada pelanggan Anda dengan mengikuti langkah-langkah berikut: + +1. Masuk ke dashboard OY! +2. Buka menu “Pengaturan” → “Notifikasi” +3. Klik Tab “Terima Uang (ke Penerima)” +4. Pilih “Aktifkan Notifikasi Transaksi yang Berhasil” +5. Pilih logo yang akan tertera di email dalam format URL. Contoh: (https://example.com/image.jpg) + - Jika Anda tidak memiliki URL logo, gunakan tools online seperti snipboard.io atau imgbb untuk mengonversi logo Anda ke URL. + - Berikut adalah contoh URL yang benar: + - Snipboard.io: https://i.snipboard.io/image.jpg + - Imgbb: https://i.ibb.co/abcdef/image.jpg +6. Simpan perubahan dengan mengklik "Simpan Perubahan" +7. Buat transaksi Link Pembayaran dan masukkan alamat email pelanggan pada kolom "Email" di bagian "Detail Pelanggan” saat membuat Link Pembayaran melalui Dashboard. Pisahkan beberapa email dengan menggunakan tanda titik koma (;). Contoh: email1@company.com;email2@company.com;email3@company.com +8. Pelanggan Anda akan menerima bukti pembayaran yang berhasil ke email yang terdaftar setelah pembayaran dilakukan. + +![Payment Link Notifications to Payer](images/acceptingPayments/payment-link/features/dashboard-notif-for-sender.webp) + +Anda juga dapat menerima bukti pembayaran ke email Anda setelah pelanggan melakukan pembayaran. Untuk mengaturnya, silakan ikuti langkah-langkah berikut: + +1. Masuk ke dashboard OY! +2. Buka menu “Pengaturan” -> “Notifikasi” +3. Klik Tab “Terima Uang (ke Pengirim)” +4. Pilih “Aktifkan Notifikasi Transaksi yang Berhasil” +5. Masukkan link logo yang akan ditampilkan pada bukti pembayaran. +6. Simpan perubahan dengan mengklik "Simpan Perubahan" +7. Anda akan menerima email untuk setiap pembayaran Link Pembayaran yang berhasil dilakukan oleh pelanggan Anda. + +**Catatan**: Jika Anda tidak memasukkan alamat email pelanggan saat membuat Link Pembayaran, OY! tidak akan mengirimkan bukti melalui email meskipun konfigurasi notifikasi sudah diaktifkan. + +![Payment Link Notifications to Merchant](images/acceptingPayments/payment-link/features/dashboard-notif-for-receiver.webp) + +#### Menyematkan Payment Link ke Website atau Aplikasi Anda +Anda dapat menyematkan Link Pembayaran langsung di website atau aplikasi Anda, sehingga pelanggan dapat menyelesaikan pembayaran tanpa dialihkan ke halaman lain. + +Ada beberapa cara untuk menampilkan Link Pembayaran di halaman Anda, berikut beberapa saran yang bisa digunakan: Pop-up (Tengah), Pop-up (Kanan), Pop-up (Kiri), Slide (Kanan). Anda dapat membaca [API Docs](https://api-docs.oyindonesia.com/#pop-seamless-payment-experience-fund-acceptance) untuk panduan implementasi lebih lanjut. + +#### Refund Pembayaran ke Pelanggan +Jika pelanggan menerima produk yang tidak sesuai, seperti rusak, atau pesanan tidak terkirim, mereka dapat meminta pengembalian dana _(refund)_. Anda dapat langsung mengembalikan pembayaran ke akun pelanggan melalui dashboard OY!. Refund dapat bersifat penuh atau sebagian. Refund penuh mengembalikan seluruh jumlah pembayaran (100%). Refund sebagian mengembalikan jumlah tertentu sesuai permintaan. + +Fitur ini tidak dipungut biaya apa pun. Namun, biaya admin dari pembayaran awal tidak akan dikembalikan oleh OY! ke saldo Anda. + +Terdapat beberapa syarat yang harus dipenuhi untuk melakukan refund: + +1. Refund hanya dapat dilakukan hingga 7 hari kalender setelah transaksi dinyatakan berhasil. +2. Saldo Anda harus mencukupi agar kami dapat memotong jumlah transaksi yang akan dikembalikan. +3. Refund hanya dapat dilakukan sekali untuk setiap transaksi yang berhasil, baik itu refund penuh maupun sebagian. +4. Refund harus dilakukan pada jam operasional, sesuai dengan metode pembayaran yang digunakan. Lihat tabel di bawah untuk detailnya. + +Saat ini, refund hanya tersedia untuk pembayaran melalui E-wallet sebagai berikut: + +|Metode Pembayaran|Fitur Refund|Jam Operasional| +| :- | :- | :- | +|

DANA

|Full, sebagian|00\.00 - 23.59 GMT+7| +|ShopeePay|Full|05\.00 - 23.59 GMT+7| +|LinkAja |Full|00\.00 - 23.59 GMT+7| +|OVO|Tidak didukung|-| + +Berikut adalah langkah-langkah untuk melakukan refund transaksi Link Pembayaran: + +1. Masuk ke dashboard OY! dengan username dan password yang telah Anda daftarkan. +2. Buka menu “Link Pembayaran”, lalu pilih “Sekali Pakai” atau “Pakai Berulang”, sesuai dengan jenis transaksi Anda. +3. Cari transaksi yang ingin direfund. Pada kolom “Tindakan”, klik tombol tiga titik, lalu pilih “Refund E-Wallet” untuk memproses refund. +4. Jika refund tidak memenuhi syarat yang disebutkan sebelumnya, akan muncul pesan error, dan Anda tidak dapat melanjutkan proses refund. +5. Jika transaksi memenuhi syarat refund, akan muncul pop-up untuk melanjutkan proses refund. +6. Untuk refund sebagian, isi jumlah yang ingin dikembalikan. +7. Pastikan saldo Anda mencukupi untuk melakukan refund. Jika saldo tidak mencukupi, akan muncul pesan error, dan Anda perlu mengisi saldo terlebih dahulu. +8. Setelah refund berhasil, status transaksi akan berubah menjadi “Direfund”. +9. Anda dapat melihat transaksi refund di halaman “Laporan Transaksi Rekening” dengan memilih “Laporan Transaksi” → “Laporan Transaksi Rekening”. + +#### _Retry_ Notifikasi/_Callback_ untuk Pembayaran Berhasil + +Jika Anda menggunakan API Link Pembayaran, OY! akan mengirimkan notifikasi/_callback_ ke sistem Anda setelah transaksi dinyatakan berhasil. Dengan demikian, Anda akan mendapatkan pemberitahuan saat pelanggan Anda telah menyelesaikan pembayaran. Namun, ada kemungkinan sistem Anda tidak menerima notifikasi. + +Dengan mengaktifkan _Retry Callback_, OY! akan mencoba mengirimkan ulang _callback_ jika sistem Anda tidak menerimanya. Anda dapat meminta pengiriman ulang _callback_ melalui Retry Callback Manual atau Retry Callback Otomatis. + +##### Cara Mengaktifkan _Retry Callback_ Manual + +Jika Anda mengaktifkan fitur *Retry Callback*, sistem akan secara otomatis mengirim ulang *callback* yang gagal. Namun, Anda juga tetap bisa mengirim ulang *callback* secara manual melalui dashboard apabila diperlukan. Berikut langkah-langkahnya: + +1. Masuk ke akun Anda +2. Buka menu “Link Pembayaran”, lalu pilih “Sekali Pakai” atau “Pakai Berulang”, sesuai dengan jenis transaksi Anda. +3. Cari transaksi yang ingin dikirim ulang *callback*\-nya, lalu klik tombol tiga titik pada kolom “Tindakan”. +4. Pastikan Anda telah mengatur Callback URL melalui “Pengaturan” → “Opsi Developer” → “Konfigurasi Callback”. +5. Masukkan URL *callback* untuk produk yang ingin Anda aktifkan. Pastikan format URL benar, lalu validasi dengan mengklik “Validasi String URL”. +6. Pastikan Anda telah whitelist IP OY\! agar sistem Anda dapat menerima *callback*: + * 54.151.191.85 + * 54.179.86.72 +7. Klik “Kirim Ulang Callback” untuk mengirim ulang *callback*, dan ulangi proses ini sesuai kebutuhan. + +![Payment Link Manual Retry Callback](images/acceptingPayments/payment-link/features/dashboard-resend-callback.webp) + +##### ***Retry Callback*** **Otomatis** + +Retry Callback Otomatis dapat membantu Anda untuk menerima *callback* ulang dalam interval tertentu jika *callback* sebelumnya tidak berhasil diterima oleh sistem Anda. OY\! akan mencoba mengirim ulang hingga 5 kali. Jika setelah 5 kali percobaan *callback* masih gagal diterima, OY\! akan mengirim notifikasi melalui email. Anda dapat mendaftarkan hingga 6 penerima email, yang dapat dikonfigurasi melalui dashboard. + +Interval *Callback*: + +*Realtime* → 1 menit (setelah percobaan awal) → 2 menit (setelah percobaan pertama) → 13 menit (setelah percobaan kedua) → 47 menit (setelah percobaan ketiga) + +OY\! mengirimkan *callback* pertama setelah transaksi berhasil. Jika sistem Anda gagal menerimanya, OY\! akan langsung mengirimkan *callback* ulang pertama. Jika *callback* masih gagal, OY\! akan mengirimkan *callback* ulang kedua 1 menit setelah *timeout* atau menerima respons gagal dari sistem Anda. Proses ini akan berlanjut hingga *callback* berhasil diterima atau semua percobaan *callback* telah dilakukan. + +Retry Callback Otomatis tidak aktif secara default. Berikut cara mengaktifkannya: + +1. Masuk ke akun Anda +2. Buka menu “Pengaturan”, lalu pilih “Opsi Developer”. +3. Pilih tab “Konfigurasi Callback”. +4. Masukkan URL *callback* untuk produk yang ingin Anda aktifkan. Pastikan format URL benar, lalu validasi dengan mengklik “Validasi String URL”. +5. Untuk mengaktifkan Retry Callback Otomatis, centang “Aktifkan Retry Callback Otomatis” untuk produk terkait. Masukkan email penerima yang akan menerima notifikasi jika callback gagal setelah semua percobaan dilakukan. +6. Pastikan Anda telah whitelist IP OY\! agar sistem dapat menerima *callback*: +* 54.151.191.85 +* 54.179.86.72 +7. Pastikan sistem Anda menerapkan *idempotency logic* dengan menggunakan parameter “tx\_ref\_number” sebagai *idempotency key* untuk mencegah *callback* yang sama diproses sebagai pembayaran yang berbeda. +8. Simpan perubahan + +![Payment Link Automatic Retry Callback](images/acceptingPayments/payment-link/features/dashboard-developer-option.webp) + +#### Multi Entity Management +Multi Entity Management adalah fitur yang dapat membantu Anda mengelola beberapa akun OY\! dalam satu entitas. Akun yang berperan sebagai admin disebut Main Entity, sedangkan akun yang dapat dikendalikan oleh admin disebut Sub Entity. + +Dengan fitur ini, Anda dapat menerima pembayaran dari pelanggan melalui Link Pembayaran yang dibuat atas nama Sub Entity. Ketika pengguna melakukan transaksi yang berhasil, transaksi tersebut akan dicatat dalam saldo Sub Entity. Sebagai Main Entity, Anda dapat melihat saldo dan daftar transaksi Sub Entity kapan saja melalui menu Multi Entity → Laporan Penyelesaian Sub Entity. + +Silakan lihat bagian [Multi Entity Management](https://docs.oyindonesia.com/id/#multi-entity-management-tutorial-dashboard-oy) untuk informasi lebih lanjut. + +### Cara Mengaktifkan Fitur Link Pembayaran +Berikut adalah panduan untuk mengaktifkan fitur Link Pembayaran: + +1. Buat akun di OY\! +2. Lakukan verifikasi akun dengan mengisi formulir verifikasi. Pastikan Anda mencentang produk “Terima Uang”, karena Link Pembayaran termasuk dalam kategori produk ini. +3. Tim OY\! akan meninjau dan memverifikasi formulir serta dokumen yang telah Anda kirimkan. +4. Setelah verifikasi disetujui, atur informasi rekening bank penerima untuk menerima saldo dari OY\!. + **Catatan Penting**: Pastikan informasi rekening bank penerima benar, karena Anda hanya dapat mengaturnya sekali saja melalui dashboard demi alasan keamanan. +5. Secara default, Anda akan mendapatkan beberapa metode pembayaran, termasuk semua transfer bank (kecuali BCA). +6. Metode pembayaran lain seperti QRIS, E-Wallet, dan BCA memerlukan proses *onboarding* tambahan agar dapat digunakan. Silakan lihat panduan lebih lanjut di: + * [Aktivasi E-Wallet](https://docs.oyindonesia.com/#e-wallet-payment-methods) + * [Aktivasi QRIS](https://docs.oyindonesia.com/#qris-payment-methods) + * [Aktivasi VA BCA](https://docs.oyindonesia.com/#bank-transfer-virtual-account-payment-methods) + +Jika Anda ingin menggunakan API Link Pembayaran, Anda perlu melakukan langkah tambahan berikut: + +1. Kirimkan alamat IP dan URL Callback ke perwakilan bisnis Anda atau melalui email ke business.support@oyindonesia.com. +2. OY\! akan mengirimkan Production API Key sebagai otorisasi API melalui perwakilan bisnis Anda. + **Catatan:** Key Staging/Demo API dapat diakses melalui dashboard dengan masuk ke mode “Demo”, lalu temukan API key di menu kiri bawah. +3. Integrasikan API ke sistem Anda dengan mengikuti panduan di [API Docs Link Pembayaran](https://api-docs.oyindonesia.com/#api-create-payment-checkout) + +Setelah semua langkah di atas selesai, Anda siap untuk membuat Link Pembayaran. + +### Cara Membuat Link Pembayaran +Anda dapat membuat Link Pembayaran melalui dashboard. Selain itu, Anda juga bisa membuat melalui API, tetapi hanya bisa untuk link sekali pakai. Berikut adalah panduan untuk membuat Link Pembayaran melalui dashboard: + +1. Masuk ke akun dashboard OY\! +2. Pilih mode yang sesuai. Jika Anda ingin membuat untuk transaksi sebenarnya, pilih "Production" pada sidebar. Jika Anda ingin membuat untuk pengujian/demo, pilih "Coba di Demo". +3. Buka menu "Terima Uang", lalu pilih "Link Pembayaran". Pilih "Sekali Pakai" atau "Pakai Berulang" sesuai dengan jenis yang ingin dibuat. +4. Klik "Buat Link Pembayaran". +5. Akan muncul pop-up untuk mengisi detail link. Silakan lihat tabel di bawah untuk penjelasan setiap kolom. +6. Klik "Simpan". +7. Setelah Link Pembayaran berhasil dibuat, Anda dapat meninjau dan membagikannya ke pelanggan. + +![Payment Link Creation](images/acceptingPayments/payment-link/creating-payment-link/creation.webp) + +| Kolom | Deskripsi | +|:---------------------|:--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| Nominal | Jumlah pembayaran yang akan ditampilkan di laman pembayaran. | +| Deskripsi (opsional) | Anda dapat menjelaskan konteks pembayaran kepada pelanggan Anda melalui kolom deskripsi. | +| ID Transaksi Partner | ID unik yang dapat Anda berikan untuk mengidentifikasi suatu transaksi. | +| Tujuan Dana Masuk |

Hanya tersedia jika Anda menggunakan Multi Entity Management.

Anda dapat memilih antara "Saldo Saya" atau "Saldo Sub Entity":

Saldo Saya: Setelah transaksi berhasil, dana akan masuk ke akun saldo Anda.

Saldo Sub Entity: Setelah transaksi berhasil, dana akan masuk ke saldo Sub Entity yang Anda pilih.

| +| Detail Pelanggan | Detail pelanggan yang dapat Anda isi, berupa: Nama Pelanggan, Nomor Telepon, Email, dan Catatan. Jika alamat email diisi, kami akan mengirimkan Link Pembayaran ke email tersebut. | +| Tipe Nominal |

Anda dapat memilih tipe nominal *open amount* atau *closed amount*.

*Open amount:* Anda dapat menerima pembayaran sampai maksimal sebesar nominal yang Anda tentukan.

*Closed Amount:* Anda hanya dapat menerima pembayaran sejumlah nominal yang ditentukan.

| +| Metode Pembayaran | Metode pembayaran yang dapat Anda pilih untuk diaktifkan. Metode pembayaran yang tersedia meliputi transfer bank, E-wallet, gerai ritel, kartu debit/kedit, dan QRIS. | +| Jenis Biaya Admin |

Jika biaya admin termasuk dalam nominal, maka akan dipotong dari pembayaran pelanggan.

Jika tidak termasuk, biaya admin akan ditambahkan ke total yang dibayar pelanggan.

| +| Tanggal Kedaluwarsa |

Setelah kedaluwarsa, pelanggan tidak dapat membuka link lagi.

Secara default, waktu kedaluwarsa adalah 24 jam. Anda dapat menyesuaikan waktu kedaluwarsa berdasarkan hari dan/atau jam.

Khusus untuk Link Pembayaran Pakai Berulang, Anda dapat menetapkan waktu kedaluwarsa sebagai "Lifetime", yang berarti link tidak memiliki batas waktu dan dapat menerima pembayaran kapan saja, kecuali dinonaktifkan secara manual.

| + + +### Menyelesaikan Pembayaran +Setelah berhasil membuat link, Anda dapat membagikan link tersebut kepada pelanggan Anda. Pelanggan Anda dapat membuka link melalui browser di desktop atau mobile. Berikut langkah-langkah bagi pelanggan untuk menyelesaikan pembayaran melalui Link Pembayaran: + +1. Pelanggan akan mengisi atau mengubah nominal transaksi (hanya tersedia untuk transaksi *open amount*). +2. Memilih metode pembayaran yang diinginkan. +3. Mengisi detail pelanggan, termasuk Nama Pelanggan, Email, Nomor Telepon, dan Catatan. Semua kolom bersifat opsional kecuali Nama Pelanggan. +4. Mengonfirmasi metode pembayaran dengan mengklik “Bayar”. +5. OY\! akan menampilkan informasi pembayaran sesuai metode yang dipilih: + * Transfer Bank: Menampilkan Nomor Rekening dan Jumlah Transfer. + * QRIS: Menampilkan kode QR yang dapat diunduh atau langsung dipindai. + * E-wallet: Pelanggan akan diarahkan ke aplikasi E-wallet (DANA, LinkAja, ShopeePay) atau menerima notifikasi dari aplikasi E-wallet (OVO). + * Kartu Kredit & Debit: Pelanggan akan diarahkan untuk mengisi nomor kartu, tanggal kedaluwarsa, dan CVV. +6. Perlu diperhatikan bahwa setiap metode pembayaran memiliki batas waktu pembayaran yang berbeda. Silakan merujuk ke tabel berikut untuk informasi lebih lanjut. +7. Untuk melakukan simulasi transaksi demo, silakan merujuk ke bagian berikut: + * [Simulasi pembayaran Virtual Account](https://docs.oyindonesia.com/#bank-transfer-virtual-account-payment-methods) + * [Simulasi pembayaran Kode Unik](https://docs.oyindonesia.com/#bank-transfer-unique-code-payment-methods) + * [Simulasi pembayaran E-wallet](https://docs.oyindonesia.com/#e-wallet-payment-methods) + * [Simulasi pembayaran Kartu](https://docs.oyindonesia.com/#cards-payment-methods-payment-methods) + * Catatan: Simulasi transaksi QRIS saat ini belum tersedia. +8. Status pada Link Pembayaran akan berubah menjadi berhasil setelah pembayaran dilakukan. Jika status transaksi tidak otomatis diperbarui, pelanggan dapat memeriksa statusnya langsung di halaman Link Pembayaran. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Metode PembayaranBatas Waktu Bayar
Transfer BankVirtual Accounthingga 24 jam
Kode Unikhingga 3 jam
E-WalletShopeePayhingga 60 menit
LinkAja5 menit
DANAhingga 60 menit
OVOhingga 55 detik
QRIShingga 30 menit
Kartu Debit & Kredit60 menit
+ +Batas waktu metode pembayaran berbeda dengan batas waktu kedaluwarsa Link Pembayaran. Batas waktu metode pembayaran dihitung sejak pelanggan mengkonfirmasi metode pembayaran yang dipilih. Sementara itu, batas waktu kedaluwarsa Link Pembayaran dihitung sejak link dibuat. Anda hanya dapat menyesuaikan batas waktu kedaluwarsa Link Pembayaran. + +Contoh: Anda membuat Link Pembayaran yang menerima pembayaran melalui Virtual Account (VA) dan QRIS dengan batas waktu kedaluwarsa 2 jam. + +Pelanggan membuka link dan memilih QRIS sebagai metode pembayaran. OY! akan menghasilkan kode QR, yang dapat dibayar dalam 30 menit sebelum kedaluwarsa. Jika pelanggan tidak menyelesaikan pembayaran dalam 30 menit, kode QR akan kedaluwarsa, dan mereka harus memilih metode pembayaran lain. + +Kali ini, pelanggan memilih VA sebagai metode pembayaran. OY! akan menghasilkan nomor VA, yang berlaku selama 1 jam 30 menit karena sisa waktu kedaluwarsa Link Pembayaran adalah 1 jam 30 menit. Setelah 2 jam, Link Pembayaran akan kedaluwarsa, dan pelanggan tidak dapat mengaksesnya lagi. + +### Mengecek Status Transaksi +Semua transaksi Link Pembayaran yang dibuat akan ditampilkan di dashboard. Untuk melihat daftar transaksi yang telah dibuat, Anda bisa pergi ke ke “Link Pembayaran” → “Sekali Pakai” atau “Pakai Berulang”. + +Di dalam dashboard, Anda dapat melihat detail transaksi, termasuk informasi yang diinput saat pembuatan, status transaksi, dan nomor referensi pembayaran. Dashboard juga menyediakan fitur untuk mencari, memfilter, dan mengekspor daftar transaksi dalam berbagai format, seperti Excel (.xlsx), PDF (.pdf), dan CSV (.csv). + +Terkadang, pelanggan Anda mungkin telah menyelesaikan pembayaran, tetapi status transaksi belum diperbarui menjadi sukses. Jika hal ini terjadi, Anda dapat melakukan beberapa langkah berikut untuk memeriksa status transaksi: + +1. Pelanggan dapat langsung memeriksa status transaksi dengan memilih tombol “Cek Status” pada halaman Link Pembayaran. +2. Anda dapat mengecek status transaksi menggunakan API dengan memanggil API Check Status. Silakan merujuk ke dokumen berikut: [Check Status Payment Link \- API Docs](https://api-docs.oyindonesia.com/#api-payment-status-fund-acceptance). + +### Menerima Dana ke Saldo +Setelah pelanggan Anda melakukan pembayaran, OY\! akan memperbarui status transaksi dan mengirim notifikasi ke sistem Anda sebagai konfirmasi bahwa transaksi telah dibayar. Dana dari transaksi tersebut akan diselesaikan (*settled*) ke saldo Anda. + +Waktu penyelesaian (*settlement time*) berbeda untuk setiap metode pembayaran, mulai dari *real-time* hingga H+2 Hari Kerja tergantung pada metode yang digunakan. + + +## VA Aggregator +Bisnis sering menghadapi tantangan dalam mengelola ratusan hingga ribuan rekening bank fisik untuk berbagai keperluan. Hal ini menyebabkan biaya operasional yang tinggi, baik dari segi pemeliharaan rekening maupun waktu yang dihabiskan untuk pelaporan dan rekonsiliasi. + +Virtual Account (VA) adalah akun *dummy* yang terhubung dengan rekening bank fisik dan memiliki karakteristik serupa dengan rekening fisik. VA memungkinkan proses pelaporan dan rekonsiliasi yang lebih mudah dengan memusatkan aliran dana ke dalam satu rekening fisik. Dengan menggunakan VA, Anda dapat mengatur setiap VA untuk pelanggan atau tujuan tertentu. + +Virtual Account (VA) Aggregator adalah fitur yang dirancang khusus untuk membuat Virtual Account, memungkinkan Anda menerima pembayaran melalui transfer bank dari pelanggan.Jika Anda ingin menerima pembayaran menggunakan berbagai metode pembayaran untuk satu transaksi, sebaiknya gunakan Link Pembayaran atau Routing Pembayaran sebagai alternatif. + +Secara umum, pembuatan nomor VA untuk pelanggan dapat dilakukan melalui API VA Aggregator. Namun, jika Anda ingin membuat VA tanpa integrasi API, Anda dapat melakukannya melalui Dashboard dengan mengakses menu "Virtual Account" di bagian "Terima Uang". +### Alur VA Aggregator +![VA Aggregator Flow](images/acceptingPayments/va-aggregator/payment-flow.webp) + + +### Fitur VA Aggregator +**Pembuatan Fleksibel – via Dashboard atau API** + +Anda dapat membuat nomor VA melalui Dashboard atau API. Jika tidak memiliki sumber daya untuk mengintegrasikan API, Anda tetap bisa membuat nomor VA dan menerima pembayaran langsung melalui dashboard. + +**Dukungan Pembayaran VA dari Berbagai Bank** + +Saat ini, OY\! mendukung pembayaran VA dari 8 bank, yaitu: + +1. Bank Central Asia (BCA) +2. Bank Rakyat Indonesia (BRI) +3. Bank Mandiri +4. Bank Negara Indonesia (BNI) +5. Bank CIMB & CIMB Syariah +6. Bank SMBC +7. Bank Syariah Indonesia (BSI) +8. Bank Permata & Permata Syariah + +***Settlement*** **Cepat untuk Mayoritas Bank** + +OY\! memahami bahwa arus kas yang lancar sangat penting bagi bisnis Anda. Kami menawarkan *settlement real-time* ke saldo OY\! untuk sebagian besar bank yang didukung, sehingga dana dapat langsung digunakan tanpa menunggu lama. + +**Menyesuaikan Jenis VA Sesuai Kebutuhan** + +Anda dapat mengkonfigurasi jenis VA sesuai kebutuhan bisnis Anda, memberikan fleksibilitas lebih dalam pengelolaan transaksi pembayaran dengan detail sebagai berikut: + +| Kategori | Tipe | Deskripsi | +| :---- | :---- | :---- | +| Masa Berlaku | VA Statis (Tanpa Kedaluwarsa) | VA yang memiliki masa berlaku tidak terbatas. Akan selalu aktif sampai dinonaktifkan secara manual. | +| | VA Dinamis | VA yang memiliki periode masa berlaku tertentu. Akan selalu aktif sampai masa berlakunya habis atau dinonaktifkan secara manual. | +| Nominal Transaksi | VA *Closed Amount* | VA yang hanya menerima pembayaran dengan nominal yang ditentukan. | +| | VA *Open Amount* | VA yang dapat menerima pembayaran dengan maksimal nominal yang telah Anda tentukan saat membuat VA. | +| Jumlah Penggunaan | VA Sekali Pakai | VA yang hanya bisa menerima satu kali pembayaran. | +| | VA Pemakaian Berulang | VA yang hanya kedaluwarsa ketika mencapai tanggal kedaluwarsa atau ketika dinonaktifkan secara manual. Anda juga dapat menyesuaikan batas maksimum pembayaran. VA Pemakaian Berulang dengan batas maksimum pembayaran yang disesuaikan akan kedaluwarsa setelah batas pembayaran terlampaui meskipun belum mencapai waktu kedaluwarsa. | +| Nomor VA | Nomor VA *Custom* | Anda dapat mempersonalisasi nomor akhir VA dengan angka yang Anda inginkan (misalnya, nomor telepon atau nomor tagihan pengguna akhir Anda). Untuk mengaktifkan penyesuaian nomor VA, silakan hubungi perwakilan bisnis Anda. Anda dapat merujuk ke [API Docs \- Create Customized VA Number.](https://api-docs.oyindonesia.com/#create-customized-va-va-aggregator) Khusus untuk fitur ini, saat ini kami hanya mendukung bank BRI dan CIMB. | +| | *Predetermined* | OY\! akan membuat nomor VA secara random atas nama Anda. Anda dapat merujuk ke [API Docs \- Create VA Number](https://api-docs.oyindonesia.com/#create-va-va-aggregator). | + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
KategoriTipeDeskripsi
Masa BerlakuVA Statis (Tanpa Kedaluwarsa) VA yang memiliki masa berlaku tidak terbatas. Akan selalu aktif sampai dinonaktifkan secara manual.
VA Dinamis VA yang memiliki periode masa berlaku tertentu. Akan selalu aktif sampai masa berlakunya habis atau dinonaktifkan secara manual.
Nominal TransaksiVA Closed AmountVA yang hanya menerima pembayaran dengan nominal yang ditentukan.
VA Open AmountVA yang dapat menerima pembayaran dengan maksimal nominal yang telah Anda tentukan saat membuat VA.
Jumlah PenggunaanVA Sekali PakaiVA yang hanya bisa menerima satu kali pembayaran.
VA Pemakaian BerulangVA yang hanya kedaluwarsa ketika mencapai tanggal kedaluwarsa atau ketika dinonaktifkan secara manual. Anda juga dapat menyesuaikan batas maksimum pembayaran. VA Pemakaian Berulang dengan batas maksimum pembayaran yang disesuaikan akan kedaluwarsa setelah batas pembayaran terlampaui meskipun belum mencapai waktu kedaluwarsa.
Nomor VACustom

Anda dapat mempersonalisasi nomor akhir VA dengan angka yang Anda inginkan (misalnya, nomor telepon atau nomor tagihan pengguna akhir Anda). Untuk mengaktifkan penyesuaian nomor VA, silakan hubungi perwakilan bisnis Anda. Anda dapat merujuk ke API Docs - Create Customized VA Number

Khusus untuk fitur ini, saat ini kami hanya mendukung bank BRI dan CIMB.

PredeterminedOY! akan membuat nomor VA secara random atas nama Anda. Anda dapat merujuk ke API Docs - Create VA Number. +
+ +1. **Kemampuan untuk Memperbarui VA** + + Setelah nomor VA dibuat, Anda masih bisa mengubah beberapa parameter berikut: + - Nominal VA (nominal) → Mengubah jumlah nominal pembayaran. + - Jenis Penggunaan (is\_single\_use) → Mengubah VA dari Sekali Pakai ke Pakai Berulang atau sebaliknya. + - Email (email) → Memperbarui alamat email + - Batas Transaksi (trx\_counter) → Menentukan jumlah pembayaran yang dapat diterima oleh nomor VA. Parameter ini hanya bisa digunakan oleh VA Pemakaian Berulang. + - Waktu Kedaluwarsa Transaksi (trx\_expired\_time) → Mengatur batas waktu transaksi VA. + - Waktu Kedaluwarsa VA (expired\_time) → Menetapkan kapan VA tidak lagi berlaku. Waktu ini harus sama atau lebih lama dari trx\_expired\_time. + - Nama yang Ditampilkan (username\_display) → Nama VA yang akan muncul saat pelanggan memasukkan nomor VA di aplikasi perbankan mereka. + + **Catatan:** Setelah VA diperbarui, konfigurasi baru akan langsung diterapkan, dan pengaturan sebelumnya tidak lagi berlaku. + +2. ***Callback* Otomatis & Retry *Callback*** + + Anda akan menerima *callback* untuk setiap pembayaran VA yang sukses melalui API. Jika *callback* gagal diterima, Anda bisa mengaktifkan Automatic Retry *Callback* melalui: + + Dashboard → Pengaturan → Opsi Developer → Konfigurasi Callback. + + Jika *callback* pertama gagal, sistem OY\! akan mencoba kembali hingga 5 kali. Jika semua percobaan gagal, OY\! akan mengirimkan notifikasi ke email yang telah Anda atur. Anda juga akan menerima *callback* untuk setiap transaksi yang berhasil diselesaikan ke saldo Anda. + +3. **Nominal Minimum dan Maksimum Transaksi VA** + + * Nominal minimum transaksi VA: Rp10.000 (untuk *closed amount*). + * nominal maksimum transaksi VA: Tergantung pada kebijakan masing-masing bank. Dengan detail sebagai berikut: + +|Nama Bank|Nominal maksimum per transaksi | +| :-: | :-: | +|Bank Central Asia (BCA) |Rp 50,000,000| +|Bank Negara Indonesia (BNI) |Rp 50,000,000| +|Bank Rakyat Indonesia (BRI) |Rp 500,000,000| +|Bank Mandiri |Rp 500,000,000| +|Bank CIMB |Rp 500,000,000| +|Bank SMBC |Rp 100,000,000| +|Bank Syariah Indonesia (BSI)|Rp 50,000,000| +|Bank Permata|Rp 500,000,000| + + +### Use Cases + +![VA Aggregator Use Case](images/acceptingPayments/va-aggregator/use-cases.webp) + +### Cara Mengaktifkan +Berikut adalah langkah-langkah aktivasi fitur VA Aggregator: + +1. Buat akun OY\! +2. Lakukan verifikasi akun dengan mengisi formulir verifikasi. Pastikan untuk mencentang produk "Terima Uang", karena VA Aggregator merupakan bagian dari produk tersebut. +3. Tim OY\! akan meninjau dan memverifikasi formulir serta dokumen yang dikirimkan. +4. Setelah verifikasi disetujui, atur informasi rekening penerima sebagai rekening tujuan penarikan saldo. Pastikan informasi rekening penerima sudah benar, karena pengaturan ini hanya dapat dilakukan sekali melalui dashboard untuk alasan keamanan. +5. Untuk penggunaan VA BCA, Anda mungkin perlu mengirimkan dokumen tambahan, seperti Nomor Pokok Wajib Pajak (NPWP) dan Kartu Tanda Penduduk (KTP) +6. Jika memiliki pertanyaan atau kendala, silakan hubungi perwakilan bisnis OY\! atau email ke business.support@oyindonesia.com + +Jika Anda ingin menggunakan VA Aggregator via API, terdapat langkah tambahan: + +1. Kirimkan alamat IP dan URL callback Anda ke perwakilan bisnis OY\! atau melalui email ke [business.support@oyindonesia.com](mailto:business.support@oyindonesia.com). Maksimal 5 alamat IP yang dapat didaftarkan. +2. OY\! akan mengirimkan Production API Key sebagai otorisasi API melalui perwakilan bisnis Anda. + **Catatan:** Staging/Demo API Key dapat diakses melalui dashboard di mode "Demo" pada menu bagian kiri bawah. +3. Integrasikan API VA Aggregator ke sistem Anda dengan mengikuti panduan pada [API Docs \- VA Aggregator](https://api-docs.oyindonesia.com/#create-customized-va-va-aggregator) untuk memastikan implementasi berjalan dengan baik. + +### Simulasi Pembuatan Virtual Account +**Membuat Nomor VA melalui API** + +1. Masuk ke mode Demo di dashboard dengan klik tomboll “Coba di Demo” untuk masuk ke mode uji coba (staging). +2. Salin API Staging Key dari menu navigasi kiri bawah. +3. Buat nomor VA dengan mengirimkan permintaan POST ke [https://api-stg.oyindonesia.com/api/generate-static-va](https://api-stg.oyindonesia.com/api/generate-static-va). Masukkan parameter yang diperlukan sesuai dengan API Docs. +4. OY\! akan merespons dengan nomor VA yang berhasil dibuat. + +**Membuat Nomor VA melalui Dashboard** + +1. Masuk ke mode Demo di dashboard dengan klik tomboll “Coba di Demo” untuk masuk ke mode uji coba (staging). +2. Pergi ke “Terima Uang” → Virtual Account → Daftar VA. +3. Klik tombol “Buat Virtual Account” di kanan atas. +4. Pilih metode pembuatan nomor VA dengan mengunggah file Excel (sesuai format template) atau input manual dengan klik “Tambahkan Detail Virtual Account Secara Manual”. +5. Klik “Validasi” setelah mengisi semua kolom. +6. Klik “Buat” untuk mengirim permintaan. +7. Setelah berhasil, Anda akan diarahkan ke halaman Daftar VA untuk melihat nomor VA yang sudah dibuat. +8. VA Anda siap untuk digunakan + +**Simulasi *Callback* Berhasil** + +1. Masuk ke mode Demo di dashboard dengan klik tomboll “Coba di Demo” untuk masuk ke mode uji coba (staging). +2. Pergi ke “Pengaturan” → Callback Transfer Bank. +3. Pilih “Virtual Account” sebagai Jenis Transaksi. +4. Pilih nama bank dari VA yang sudah dibuat sebelumnya. +5. Masukkan nomor VA dan jumlah transaksi. Untuk VA *closed amount*, jumlah yang dimasukkan harus sesuai dengan yang telah dibuat. +6. Masukkan tanggal dan waktu pembayaran. Pastikan Tanggal & Waktu Pembuatan VA \< Tanggal & Waktu Pembayaran \< Tanggal & Waktu Kedaluwarsa + +### Cara Menggunakan Virtual Account +Melihat Daftar Virtual Account (VA) yang Dibuat + +1. Masuk ke dashboard. +2. Pergi ke: + +“Terima Uang” → “Virtual Account” → “Daftar VA” + +Di halaman ini, Anda dapat melihat VA yang sudah Anda buat dengan detail informasi berikut: Nomor VA yang telah dibuat, Status pembayaran, Jumlah transaksi, Jenis VA, serta Jumlah transaksi yang telah diselesaikan. Anda juga dapat mengekspor daftar VA beserta detailnya dalam format PDF, Excel, atau CSV sesuai kebutuhan. + +![VA Aggregator Monitor Created Transactions](images/acceptingPayments/va-aggregator/viewing-list-of-created-va.webp) + +Melihat Daftar Pembayaran Virtual Account (VA) + +1. Masuk ke dashboard. +2. Pergi ke: + +“Terima Uang” → “Virtual Account” → “Pembayaran Berlangsung” + +Di halaman ini, Anda dapat melihat transaksi yang sudah diterima dari nomor VA yang sudah Anda buat dengan detail informasi berikut: *Timestamp* transaksi, Status transaksi, Jumlah pembayaran, Biaya admin, dan Informasi tambahan lainnya. Anda juga dapat mengekspor daftar VA beserta detailnya dalam format PDF, Excel, atau CSV sesuai kebutuhan. + +![VA Aggregator Monitor Incoming Transactions](images/acceptingPayments/va-aggregator/viewing-list-of-incoming-payment.webp) + + +### Detail Virtual Account Tiap Bank +| Nama Bank | Kode Bank | Open Amount | Closed Amount | Max. Masa Berlaku | +| ----- | ----- | ----- | ----- | ----- | +| BCA | 014 | Ya | Ya | Lifetime | +| BNI | 009 | Sebagian\* | Ya | Lifetime | +| BRI | 002 | Ya | Ya | Lifetime | +| Bank Mandiri | 008 | Ya | Ya | Lifetime | +| Bank CIMB | 022 | Ya | Ya | Lifetime | +| Bank SMBC | 213 | Ya | Ya | Lifetime | +| BSI | 451 | Tidak | Ya | 70 hari setelah dibuat. | +| Bank Permata | 013 | Ya | Ya | Lifetime | + + +\*Untuk informasi lebih lanjut, silakan hubungi perwakilan bisnis kami. + +**Catatan**: Tidak ada batas waktu minimum untuk masa berlaku Virtual Account (VA). Namun, disarankan untuk menetapkan masa berlaku yang wajar agar pelanggan memiliki cukup waktu untuk menyelesaikan pembayaran dengan nyaman. + +### Daftar Metode Pembayaran untuk VA + +Pelanggan Anda dapat melakukan pembayaran melalui VA dengan metode berikut: + +| Nama Bank | SKN | RTGS | ATM | Mobile Banking & Internet Banking Intrabank | Internet Banking Interbank | Mobile Banking Interbank | +| :---- | :---- | :---- | :---- | :---- | :---- | :---- | +| Bank Mandiri | Ya | Ya | Ya | Ya | Ya | Ya | +| BRI | Ya | Ya | Ya | Ya | Tidak | Ya | +| BNI | Ya | Ya | Ya | Ya | Tidak | Ya | +| Permata | Ya | Ya | Ya | Ya | Tidak | Ya | +| CIMB Niaga/CIMB Niaga Syariah | Ya | Ya | Ya | Ya (Mobile Banking), Tidak (Internet Banking) | Tidak | Ya | +| BCA | Tidak | Tidak | Ya | Ya | Tidak | Tidak | +| SMBC | Ya | Tidak | Ya | Ya (Mobile Banking), Tidak (Internet Banking) | Tidak | Ya | +| BSI | Tidak | Tidak | Ya | Ya | Ya | Ya | + +## API E-Wallet Aggregator +API E-Wallet Aggregator adalah fitur yang membantu Anda untuk menerima pembayaran dari berbagai macam E-wallet. Dengan satu integrasi, maka Anda dapat mengakses semua E-wallet yang tersedia di OY\!. + +### Alur API E-Wallet Aggregator + +![E-wallet Aggregator Flow](images/acceptingPayments/api-e-wallet-aggragator/payment-flow.webp) + +### Fitur E-Wallet Aggregator +Produk E-wallet Aggregator kami mendukung transaksi E-wallet dari provider berikut: + +* ShopeePay +* LinkAja +* DANA +* OVO + +#### Pantau Transaksi via Dashboard +Semua transaksi E-wallet yang telah dibuat dapat dipantau melalui Dashboard OY\!. Masuk ke dashboard OY\! dan pergi ke tab E-Wallet. Di dalam dashboard, Anda dapat melihat detail setiap transaksi, termasuk informasi yang diinput saat pembuatan, status transaksi, dan nomor referensi pembayaran. Gunakan fitur pencarian dan filter untuk menemukan transaksi tertentu dengan cepat. Transaksi dapat diekspor dalam berbagai format: Excel (.xlsx), PDF (.pdf), dan CSV (.csv). + +![Monitor E-wallet Aggregator Transaction](images/acceptingPayments/api-e-wallet-aggragator/dashboard-selesai.webp) + +#### Cara Mengaktifkan +Berikut adalah panduan untuk mengaktifkan fitur API E-Wallet: + +1. Buat akun di OY\! +2. Lakukan verifikasi akun dengan mengisi formulir verifikasi. Pastikan Anda mencentang produk “Terima Uang”, karena API E-Wallet termasuk dalam kategori produk ini. +3. Tim OY\! akan meninjau dan memverifikasi formulir serta dokumen yang telah Anda kirimkan. +4. Setelah verifikasi disetujui, atur informasi rekening bank penerima. + **Catatan Penting**: Pastikan informasi rekening bank penerima untuk menerima saldo OY\! benar, karena Anda hanya dapat mengaturnya sekali saja melalui dashboard demi alasan keamanan. +4. Ikuti proses registrasi untuk setiap E-wallet yang ingin Anda gunakan. Panduan lengkapnya dapat ditemukan di bagian [E-Wallet Activation](https://docs.oyindonesia.com/#e-wallet-payment-methods). +5. Kirimkan alamat IP dan URL Callback ke perwakilan bisnis Anda atau melalui email ke [business.support@oyindonesia.com](mailto:business.support@oyindonesia.com). +6. OY\! akan mengirimkan Production API Key sebagai otorisasi API melalui perwakilan bisnis Anda. + **Catatan:** Key Staging/Demo API dapat diakses melalui dashboard dengan masuk ke mode “Demo”, lalu temukan API key di menu kiri bawah. +7. Integrasikan API ke sistem Anda dengan mengikuti panduan di [API Docs E-Wallet](https://api-docs.oyindonesia.com/#create-e-wallet-transaction-api-e-wallet-aggregator) + +#### Cara Membuat Transaksi E-Wallet via API +Transaksi E-Wallet hanya dapat dibuat melalui API. Ikuti langkah-langkah berikut untuk membuat transaksi E-Wallet melalui API: + +1. Integrasikan API Create E-Wallet Transaction ke dalam sistem Anda. Panduan lengkap dapat ditemukan di [Create E-Wallet \- API Docs](https://api-docs.oyindonesia.com/#https-request-create-e-wallet-transaction). +2. *Hit* API OY\! untuk membuat transaksi E-Wallet. +3. OY\! akan mengembalikan informasi yang diperlukan untuk menyelesaikan pembayaran. +* Untuk E-wallet dengan metode *redirection* (ShopeePay, DANA, LinkAja), OY\! akan mengembalikan URL E-wallet yang dapat dibagikan kepada pelanggan untuk menyelesaikan pembayaran. +* Untuk E-wallet dengan metode *push notification* (OVO), penyedia E-wallet akan mengirimkan notifikasi ke aplikasi E-wallet pelanggan untuk menyelesaikan pembayaran. + +**Catatan**: Jika Anda melakukan hit API *Create E-Wallet Transaction* di mode Staging/Demo, API akan selalu mengembalikan URL yang sama dalam respons: + +[https://pay-dev.shareitpay.in/aggregate-pay-gate](https://pay-dev.shareitpay.in/aggregate-pay-gate) + +URL ini tidak dapat digunakan untuk mensimulasikan pembayaran. Untuk simulasi pembayaran, silakan merujuk ke bagian [Simulate E-Wallet Payments \- Product Docs](https://docs.oyindonesia.com/#e-wallet-payment-methods). + +#### Bukti Pembayaran +Pelanggan Anda dapat menerima bukti pembayaran yang berhasil melalui email yang Anda berikan saat proses pembuatan transaksi. Anda dapat mengatur pengiriman bukti bayar melalui email kepada pelanggan dengan langkah-langkah berikut: + +1. Masuk ke dashboard OY\! +2. Buka menu “Pengaturan” \-\> “Notifikasi” +3. Klik Tab “Terima Uang (ke Pengirim)” +4. Pilih “Aktifkan Notifikasi Transaksi yang Berhasil” untuk API E-Wallet. (Pengaturan API E-Wallet akan muncul bila Anda sudah mengaktifkan produk API E-Wallet.) +5. Pilih logo yang akan tertera di email dalam format URL. Contoh: (https://example.com/image.jpg) +* Jika Anda tidak memiliki URL logo, gunakan tools online seperti snipboard.io atau imgbb untuk mengonversi logo Anda ke URL. +* Berikut adalah contoh URL yang benar: + Snipboard.io: [https://i.snipboard.io/image.jpg](https://i.snipboard.io/image.jpg) + Imgbb: https://i.ibb.co/abcdef/image.jpg +6. Simpan perubahan dengan mengklik "Simpan Perubahan" +7. Masukkan email pelanggan lewat parameter “email” saat membuat transaksi E-Wallet via API. +8. Pelanggan Anda akan menerima bukti pembayaran yang berhasil ke email yang terdaftar setelah pembayaran dilakukan. + +![Receipt for successful Payment](images/acceptingPayments/payment-link/features/dashboard-notif-for-sender.webp) + +**Catatan:** Jika Anda tidak memasukkan alamat email pelanggan saat membuat transaksi, OY\! tidak akan mengirimkan bukti transaksi melalui email, meskipun konfigurasi notifikasi sudah diaktifkan. Sebaliknya, jika Anda memasukkan alamat email tetapi konfigurasi notifikasi tidak diaktifkan, bukti transaksi juga tidak akan dikirimkan. + +#### *Retry* Notifikasi/*Callback* untuk Pembayaran Berhasil +OY\! akan mengirimkan notifikasi/*callback* ke sistem Anda setelah transaksi dinyatakan berhasil. Dengan demikian, Anda akan mendapatkan pemberitahuan saat pelanggan telah menyelesaikan pembayaran. Namun, ada kemungkinan sistem Anda tidak menerima notifikasi. + +Dengan mengaktifkan *Retry Callback*, OY\! akan mencoba mengirimkan ulang *callback* jika sistem Anda tidak menerimanya. Anda dapat meminta pengiriman ulang *callback* melalui Retry Callback Manual atau Retry Callback Otomatis. + +##### *Retry Callback* Manual +Retry Callback Manual membantu Anda untuk mengirim ulang *callback* secara manual untuk setiap transaksi melalui dashboard. Berikut langkah-langkahnya: + +1. Masuk ke akun Anda +2. Pastikan Anda telah mengatur Callback URL melalui “Pengaturan” → “Opsi Developer” → “Konfigurasi Callback” untuk produk E-Wallet Aggregator. +3. Pastikan Anda telah whitelist IP OY\! agar sistem Anda dapat menerima *callback*: + - 54.151.191.85 + - 54.179.86.72 +4. Buka menu “API E-Wallet”. +5. Cari transaksi yang ingin dikirim ulang *callback*\-nya, lalu klik tombol tiga titik pada kolom “Tindakan”. +6. Klik “Kirim Ulang Callback” untuk mengirim ulang *callback*, dan ulangi proses ini sesuai kebutuhan. + +![Manual Retry Callback](images/acceptingPayments/api-e-wallet-aggragator/dashboard-callback.webp) + +##### *Retry Callback* Otomatis +Ikuti langkah-langkah berikut untuk mengaktifkan *Retry Callback* Otomatis: + +1. Masuk ke akun Anda +2. Buka menu “Pengaturan”, lalu pilih “Opsi Developer”. +3. Pilih tab “Konfigurasi Callback”. +4. Masukkan URL *callback* untuk produk yang ingin Anda aktifkan. Pastikan format URL benar, lalu validasi dengan mengklik “Validasi String URL”. +5. Untuk mengaktifkan Retry Callback Otomatis, centang “Aktifkan Retry Callback Otomatis” untuk produk terkait. Masukkan email penerima yang akan menerima notifikasi jika callback gagal setelah semua percobaan dilakukan. +6. Pastikan Anda telah whitelist IP OY\! agar sistem dapat menerima *callback*: + - 54.151.191.85 + - 54.179.86.72 +7. Pastikan sistem Anda menerapkan *idempotency logic* dengan menggunakan parameter “tx\_ref\_number” sebagai *idempotency key* untuk mencegah *callback* yang sama diproses sebagai pembayaran yang berbeda. +8. Simpan perubahan + +![Automatic Retry Callback](images/acceptingPayments/api-e-wallet-aggragator/dashboard-developer-option.webp) + +#### Refund Pembayaran ke Pelanggan +Jika pelanggan menerima produk yang tidak sesuai, seperti rusak, atau pesanan tidak terkirim, mereka dapat meminta pengembalian dana (*refund)*. Anda dapat langsung mengembalikan pembayaran ke akun pelanggan melalui dashboard OY\!. Refund dapat bersifat penuh atau sebagian. Refund penuh mengembalikan seluruh jumlah pembayaran (100%). Refund sebagian mengembalikan jumlah tertentu sesuai permintaan. + +Fitur ini tidak dipungut biaya apa pun. Namun, biaya admin dari pembayaran awal tidak akan dikembalikan oleh OY\! ke saldo Anda. + +Terdapat beberapa syarat yang harus dipenuhi untuk melakukan refund: + +1. Refund hanya dapat dilakukan hingga 7 hari kalender setelah transaksi dinyatakan berhasil. +2. Saldo Anda harus mencukupi agar kami dapat memotong jumlah transaksi yang akan dikembalikan. +3. Refund hanya dapat dilakukan sekali untuk setiap transaksi yang berhasil, baik itu refund penuh maupun sebagian. +4. Refund harus dilakukan pada jam operasional, sesuai dengan metode pembayaran yang digunakan. Lihat tabel di bawah untuk detailnya. + +Saat ini, refund hanya tersedia untuk pembayaran melalui E-wallet sebagai berikut: + +| Metode Pembayaran | Fitur Refund | Jam Operasional | +| ----- | ----- | ----- | +| DANA | Full, sebagian | 00.00 \- 23.59 GMT+7 | +| ShopeePay | Full | 05.00 \- 23.59 GMT+7 | +| LinkAja | Full | 00.00 \- 23.59 GMT+7 | +| OVO | Tidak didukung | \- | + +Jika Anda menggunakan API Refund, OY\! akan mengirimkan notifikasi ke sistem Anda melalui callback setelah transaksi berhasil direfund. Untuk detail lebih lengkapnya, silakan merujuk ke [Refund Callback \- API Docs](https://api-docs.oyindonesia.com/#callback-parameters-e-wallet-refund-callback). Transaksi refund juga akan ditampilkan pada Laporan Penyelesaian Anda. + +Anda juga dapat memeriksa status permintaan refund melalui API. Untuk panduan lebih lanjut, silakan lihat [Refund Check Status \- API Docs](https://api-docs.oyindonesia.com/#get-e-wallet-refund-status-api-e-wallet-aggregator). + +### Menyelesaikan Transaksi E-Wallet +Setiap penyedia E-wallet memiliki metode yang berbeda untuk menyelesaikan transaksi. Pertama adalah metode *redirection* (ShopeePay, LinkAja, DANA), pelanggan akan diarahkan ke halaman E-wallet untuk menyelesaikan pembayaran. Kedua adalah metode *push notification* (OVO), penyedia E-wallet akan mengirimkan notifikasi ke aplikasi OVO pelanggan untuk menyelesaikan pembayaran. Baca panduan lengkapnya di sini: + +* Menyelesaikan transaksi E-wallet via ShopeePay [Payment Journey](https://docs.oyindonesia.com/#e-wallet-payment-methods) +* Menyelesaikan transaksi E-wallet via LinkAja [Payment Journey](https://docs.oyindonesia.com/#e-wallet-payment-methods) +* Menyelesaikan transaksi E-wallet via DANA [Payment Journey](https://docs.oyindonesia.com/#e-wallet-payment-methods) +* Menyelesaikan transaksi E-wallet via OVO [Payment Journey](https://docs.oyindonesia.com/#e-wallet-payment-methods) + +Untuk menguji transaksi di mode demo, silakan merujuk ke [Simulate E-Wallet Payments \- Product Docs](https://docs.oyindonesia.com/#e-wallet-payment-methods). + +### Mengecek Status Transaksi +Semua transaksi E-wallet yang dibuat akan ditampilkan di dashboard. Untuk melihat daftar transaksi yang telah dibuat, Anda bisa pergike menu “API E-wallet”. + +Di dalam dashboard, Anda dapat melihat detail transaksi, termasuk informasi yang diinput saat pembuatan, status transaksi, dan nomor referensi pembayaran. Dashboard juga menyediakan fitur untuk mencari, memfilter, dan mengekspor daftar transaksi dalam berbagai format, seperti Excel (.xlsx), PDF (.pdf), dan CSV (.csv). + +Terkadang, pelanggan mungkin sudah menyelesaikan pembayaran, tetapi status transaksi belum diperbarui menjadi sukses. Oleh karena itu, disarankan untuk secara berkala memeriksa status transaksi menggunakan [Check Status E-Wallet API](https://api-docs.oyindonesia.com/#https-request-check-e-wallet-transaction-status). + +### Menerima Dana ke Saldo +Setelah pelanggan menyelesaikan pembayaran, OY\! akan secara otomatis memperbarui status transaksi dan mengirimkan *callback* ke sistem Anda untuk memberi tahu bahwa transaksi telah dibayar. Selain itu, OY\! juga akan menyelesaikan (*settle*) dana ke saldo OY\! Anda. Waktu *settlement* dana bervariasi tergantung pada penyedia layanan E-wallet, dengan estimasi penyelesaian dalam H+1 hingga H+2 hari kerja setelah pembayaran diterima. + + +## API Routing Pembayaran +API Routing Pembayaran adalah layanan yang dapat membantu Anda menerima pembayaran dari pelanggan Anda dan mengirim uang ke penerima dalam satu API terintegrasi. Dengan layanan ini, Anda dapat secara otomatis mendistribusikan dana ke beberapa penerima setelah menerima pembayaran dari pelanggan. Menggunakan API Routing Pembayaran dapat membantu Anda menghemat waktu, karena menyediakan dua layanan sekaligus dalam satu integrasi. + + + + + + + + + + + + + + + + + + + + + + + + +
Tipe Routing PembayaranFitur-Fitur
Tipe TransaksiPayment Aggregator

Hanya menerima pembayaran dari pelanggan.

API all-in-one untuk menerima pembayaran melalui transfer bank, E-wallet, QRIS, dan kartu.

Payment RoutingMenerima pembayaran dari pelanggan dan secara otomatis meneruskan dana ke beberapa penerima menggunakan berbagai metode, seperti VA, E-wallet, QRIS, dll .
Tipe Penerimaan UangTanpa interface +

Anda memiliki halaman checkout sendiri, dan OY\! menyediakan detail pembayarannya.

+

+

OY! menyediakan detail pembayaran setelah transaksi dibuat (misalnya, nomor VA, URL E-wallet, URL kode QR, dll.).

+

+

Mendukung hanya satu metode pembayaran dalam satu transaksi (Single Payment & Direct Payment).

+
Dengan interface +

Menggunakan halaman checkout bawaan OY! (Link Pembayaran).

+

+

OY! menyediakan Link Pembayaran setelah dibuat.

+

+

Mendukung berbagai metode pembayaran dalam satu transaksi.

+
+ +#### Use Cases +**Payment Aggregator** + +**Single Payments** + +Single payment memungkinkan pelanggan menyelesaikan pembayaran dengan mudah. Tersedia untuk Bank Transfer (Virtual Account & Unique Code), E-Wallet, QRIS, dan Kartu Debit/Kredit. + +**Direct Payments** + +Direct payment memerlukan *account linking*, di mana pelanggan harus menghubungkan akun pembayaran mereka ke sistem Anda sebelum menyelesaikan pembayaran. Anda dapat menggunakan API *Account Linking* untuk proses ini. + +Direct payment menawarkan pengalaman pembayaran yang lebih mudah.. Setelah akun terhubung, pelanggan tidak perlu membuka atau dialihkan ke aplikasi penyedia pembayaran untuk menyelesaikan transaksi. + +Saat ini, fitur ini hanya tersedia untuk E-wallet ShopeePay. + +**Contoh *Use Case* Routing Pembayaran** + +**Investasi** + +Regulasi OJK tidak mengizinkan aplikasi investasi menyimpan saldo pengguna. Anda dapat menggunakan routing pembayaran untuk menerima dana dari investor dan langsung mengirimkannya ke bank kustodian. + +**E-Commerce** + +Terima pembayaran barang dari pelanggan dan langsung kirimkan bagian pembayaran rekening bank merchant. + +**Pendidikan** + +Terima pembayaran uang sekolah dari orang tua dan secara otomatis kirimkan biaya administrasi ke rekening bank sekolah. + +**Aplikasi Pinjaman** + +Terima pembayaran cicilan dari peminjam dan langsung salurkan dana ke rekening penampungan pemberi pinjaman atau peminjam. + +### Alur Routing Pembayaran + +![Payment Routing Aggregator Scheme](images/acceptingPayments/api-payment-routing/payment-flow-without-ui-scheme.webp) + +![Payment Routing Payment Link Scheme](images/acceptingPayments/api-payment-routing/payment-flow-with-ui-scheme.webp) + +### Fitur Routing Pembayaran + +#### Mendukung Berbagai metode Pembayaran +OY\! mendukung berbagai metode pembayaran dalam API Routing Pembayaran, termasuk: + +1. Transfer Bank + - Virtual Account: BCA, BNI, BRI, Mandiri, CIMB, SMBC, BSI, Permata + - Kode Unik: BCA +2. E-Wallet + - Single Payments: ShopeePay, DANA, LinkAja + - Direct Payments: ShopeePay +3. Kartu Debit/Kredit: Visa, Mastercard, JCB +4. QRIS + +#### Kirim Dana ke Banyak Penerima Secara *Real-Time* +Setelah menerima pembayaran dari pelanggan, OY\! dapat langsung menerima dana hingga ke 10 penerima tanpa menunggu proses *settlement*, selama saldo di akun Anda mencukupi. + +Perlu diperhatikan bahwa beberapa metode pembayaran mungkin tidak langsung masuk ke saldo secara *real-time* (misalnya QRIS dan E-wallet). Oleh karena itu, pastikan saldo Anda mencukupi untuk memproses pengiriman dana. + +#### Gunakan Fitur Link Pembayaran untuk Menerima Dana +Terdapat dua jenis metode penerimaan dana: Dengan *Interface* dan Tanpa *Interface*. Tanpa *Interface* cocok digunakan jika Anda memiliki halaman checkout sendiri dan hanya membutuhkan detail pembayaran untuk menyelesaikan transaksi. Berikut adalah informasi pembayaran yang akan Anda terima setelah transaksi berhasil dibuat: + +1. Transfer Bank \- Virtual Account: bank tujuan, nomor VA, dan jumlah transaksi. +2. Transfer Bank \- Kode Unik: bank tujuan, nomor rekening, nama rekening, nominal tagihan awal, nominal unik, dan total nominal. +3. QRIS: URL untuk mengakses kode QR. +4. E-Wallet: link untuk mengarahkan pelanggan ke E-wallet yang dipilih. +5. Kartu debit/kredit: link untuk mengarahkan pelanggan agar mengisi detail kartu dan melanjutkan pembayaran. + +Dengan *Interface* cocok jika Anda tidak memiliki halaman checkout sendiri. Anda dapat menggunakan halaman checkout bawaan OY\! (Link Pembayaran) untuk transaksi Routing Pembayaran. Untuk mengaktifkan fitur ini, cukup isi parameter "need\_frontend" dengan "TRUE" dalam API pembuatan transaksi. + +Pelajari lebih lanjut tentang Link Pembayaran di [Payment Link \- Product Docs](https://docs.oyindonesia.com/#payment-link-accepting-payments). + + +#### Buat Transaksi E-Wallet *Direct Payment* +API Routing Pembayaran mendukung transaksi *Direct Payment*, di mana pelanggan tidak perlu dialihkan ke aplikasi atau situs web penyedia pembayaran untuk menyelesaikan transaksi. Hal ini memberikan pengalaman pembayaran yang lebih mudah dan efisien. + +Saat ini, fitur *Direct Payment* hanya tersedia untuk E-wallet ShopeePay. + +Pelajari perbedaan antara *Single Payment* dan *Direct Payment* di [E-Wallet Payment Type](https://docs.oyindonesia.com/#e-wallet-payment-methods). + +#### Lacak dan Pantau Transaksi +Semua transaksi Routing Pembayaran yang dibuat akan ditampilkan di Dashboard OY\!. Untuk melihat daftar transaksi, silakan pergi ke menu Routing Pembayaran. + +Di dalam dashboard, Anda dapat melihat detail transaksi, yaitu Informasi transaksi yang diinput saat pembuatan, status transaksi, serta nomor referensi pembayaran\* + +Dashboard juga menyediakan fitur untuk mencari, memfilter, dan mengekspor daftar transaksi dalam berbagai format, seperti Excel (.xlsx), PDF (.pdf), dan CSV (.csv). + +![Payment Routing Monitoring Transactions](images/acceptingPayments/api-payment-routing/dashboard.webp) + +\*Nomor Referensi Pembayaran adalah nomor identifikasi untuk pembayaran yang berhasil ketika pelanggan menyelesaikan transaksi QRIS. Nomor referensi ini juga ditampilkan pada bukti transaksi pelanggan. Fitur ini hanya tersedia untuk transaksi QRIS. + +#### Gunakan Nomor Virtual Account yang Sama untuk Berbagai Transaksi +Satu pelanggan mungkin melakukan pembayaran untuk beberapa transaksi dengan menggunakan bank yang sama. Dengan membuar nomor VA yang sama untuk transaksi yang berbeda, pelanggan dapat lebih mudah melakukan pembayaran karena mereka bisa menyimpan nomor VA di aplikasi mobile banking mereka. + +Namun, nomor VA yang sama hanya dapat digunakan untuk satu transaksi aktif dalam satu waktu. + +### Cara Mengaktifkan Fitur Routing Pembayaran +Ikuti langkah-langkah berikut untuk melakukan registrasi dan melakukan transaksi Routing Pembayaran: + +1. Buat akun di OY\! +2. Lakukan verifikasi akun dengan mengisi formulir verifikasi. Pastikan untuk mencentang produk Terima Uang dan Kirim Uang, karena Routing Pembayaran termasuk dalam produk ini. +3. Tim OY\! akan meninjau dan memverifikasi formulir serta dokumen yang dikirimkan. +4. Setelah verifikasi disetujui, atur informasi rekening penerima. + + **Catatan Penting:** Pastikan informasi rekening bank penerima benar, karena Anda hanya dapat mengaturnya sekali saja melalui dashboard demi alasan keamanan. +5. Secara default, Anda akan mendapatkan beberapa metode pembayaran, termasuk semua transfer bank (kecuali BCA). +6. Metode pembayaran lain seperti QRIS, E-Wallets, Kartu, dan BCA memerlukan *onboarding* tambahan agar bisa digunakan. Silakan merujuk ke panduan berikut: + - [Aktivasi E-Wallet](https://docs.oyindonesia.com/#e-wallet-payment-methods) + - [Aktivasi QRIS](https://docs.oyindonesia.com/#qris-payment-methods) + - [Aktivasi VA BCA](https://docs.oyindonesia.com/#bank-transfer-virtual-account-payment-methods) + - [Aktivasi Kartu Debit/Kredit](https://docs.oyindonesia.com/#cards-payment-methods-payment-methods) +7. Kirimkan alamat IP dan URL Callback ke perwakilan bisnis Anda atau melalui email ke [business.support@oyindonesia.com](mailto:business.support@oyindonesia.com). +8. OY\! akan mengirimkan Production API Key melalui perwakilan bisnis Anda. + + **Catatan**: Key Staging/Demo API dapat diakses melalui dashboard dengan masuk ke mode “Demo”, lalu temukan API key di menu kiri bawah. +9. Integrasikan API Routing Pembayaran ke dalam sistem Anda. Ikuti panduan di dokumentasi API: [Payment Routing \- API Docs](https://api-docs.oyindonesia.com/#payment-routing). + +### Cara Membuat Transaksi Routing Pembayaran +Setelah menyelesaikan proses registrasi, Anda dapat langsung membuat transaksi Routing Pembayaran menggunakan API. Anda bisa memilih skema tanpa *Interface* atau dengan *Interface*, tergantung pada kebutuhan Anda. + +#### Skema Tanpa *Interface* +1. Integrasikan API untuk membuat transaksi Payment Routing ke dalam sistem Anda. Untuk lebih jelasnya, silakan lihat Dokumentasi API: [Create Payment Routing \- API Docs](https://api-docs.oyindonesia.com/#https-request-create-and-update-payment-routing) +2. Hit API OY\! untuk membuat transaksi Routing Pembayaran. +3. Masukkan parameter berikut: "need\_frontend" → "FALSE" +4. Pilih satu metode pembayaran: BANK\_TRANSFER, EWALLET, QRIS, CARDS +5. Tambahkan metode pembayaran yang dipilih ke parameter "list\_enable\_payment\_method". + + **Catatan:** Hanya satu metode pembayaran yang boleh dimasukkan, jika lebih dari satu akan muncul pesan error. +6. Pilih satu bank atau penyedia pembayaran (SOF) sesuai metode yang dipilih: +* BANK\_TRANSFER: 014, 009, 002, 008, 022, 213, 011, 016, 484, 451, 013\. +* EWALLET: shopeepay\_ewallet, dana\_ewallet, linkaja\_ewallet +* QRIS: QRIS +* CARDS: CARDS +7. Tambahkan SOF yang dipilih ke parameter "list\_enable\_sof". + + **Catatan**: Hanya satu SOF yang boleh dimasukkan, jika lebih dari satu akan muncul pesan error. +8. Jika menggunakan E-wallet Direct Payment, isi parameter "use\_linked\_account" dengan: + + "TRUE" → Jika menggunakan ShopeePay Direct Payment + "FALSE" → Jika tidak menggunakan fitur ini + Pastikan [Account Linking](https://docs.oyindonesia.com/#api-account-linking-accepting-payments) sudah dilakukan sebelum membuat transaksi Direct Payment. +9. Jika ingin langsung mengirim uang setelah menerima pembayaran, isi "payment\_routing" dengan nomor rekening tujuan dan nominal dana yang akan dikirim ke masing-masing penerima +10. OY\! akan mengembalikan informasi pembayaran sesuai metode yang dipilih: + - Transfer Bank \- Virtual Account: bank tujuan, nomor VA, dan jumlah transaksi. + - Transfer Bank \- Kode Unik: bank tujuan, nomor rekening, nama rekening, nominal tagihan awal, nominal unik, dan total nominal. + - QRIS: URL untuk mengakses kode QR. + - E-Wallet: link untuk mengarahkan pelanggan ke E-wallet yang dipilih. + - Kartu debit/kredit: link untuk mengarahkan pelanggan agar mengisi detail kartu dan melanjutkan pembayaran. +11. Tampilkan detail pembayaran kepada pelanggan di dalam aplikasi Anda. + +#### Skema Dengan *Interface* +Jika Anda ingin menggunakan halaman checkout bawaan dari OY\! (Link Pembayaran), ikuti langkah-langkah berikut: + +1. Integrasikan API untuk membuat transaksi Payment Routing ke dalam sistem Anda. Untuk lebih jelasnya, silakan lihat Dokumentasi API: [Create Payment Routing \- API Docs](https://api-docs.oyindonesia.com/#https-request-create-and-update-payment-routing) +2. Hit API OY\! untuk membuat transaksi Routing Pembayaran. +3. Masukkan parameter berikut: "need\_frontend" → "TRUE" +4. Pilih satu metode pembayaran: BANK\_TRANSFER, EWALLET, QRIS, CARDS +5. Tambahkan daftar metode pembayaran yang dipilih ke dalam parameter "list\_enable\_payment\_method". + + **Catatan**: Anda bisa memasukkan lebih dari satu metode pembayaran agar pelanggan dapat memilih metode yang diinginkan. +6. Pilih bank atau penyedia pembayaran (SOF) untuk setiap metode pembayaran: + - BANK\_TRANSFER: 014, 009, 002, 008, 022, 213, 011, 016, 484, 451, 013 + - EWALLET: shopeepay\_ewallet, dana\_ewallet, linkaja\_ewallet + - QRIS: QRIS + - CARDS: CARDS +7. Tambahkan daftar SOF yang dipilih ke dalam parameter "list\_enable\_sof". + + **Catatan**: Anda bisa memasukkan lebih dari satu bank/penyedia pembayaran agar pelanggan dapat memilih metode pembayaran yang diinginkan. +8. Jika ingin langsung mengirim uang setelah menerima pembayaran, isi "payment\_routing" dengan nomor rekening tujuan dan nominal dana yang akan dikirim ke masing-masing penerima +9. OY\! akan mengembalikan URL Link Pembayaran, yang dapat Anda bagikan kepada pelanggan untuk menyelesaikan pembayaran. + + +### Cara Menyelesaikan Pembayaran + +#### Skema Tanpa *Interface* +EachSetiap metode pembayaran memiliki alur yang berbeda untuk menyelesaikan transaksi, tergantung pada kebijakan masing-masing metode pembayaran. Silakan merujuk ke panduan berikut untuk menyelesaikan transaksi berdasarkan metode pembayaran yang digunakan: + +[Menyelesaikan transaksi Transfer Bank \- Virtual Account](https://docs.oyindonesia.com/#bank-transfer-virtual-account-payment-methods) + +[Menyelesaikan transaksi Transfer Bank \- Kode Unik](https://docs.oyindonesia.com/#bank-transfer-unique-code-payment-methods) + +[Menyelesaikan transaksi QRIS](https://docs.oyindonesia.com/#qris-payment-methods) + +[Menyelesaikan transaksi E-Wallet](https://docs.oyindonesia.com/#e-wallet-payment-methods) + +[Menyelesaikan transaksi Kartu Debit/Kredit](https://docs.oyindonesia.com/#cards-payment-methods-payment-methods) + +Untuk melakukan simulasi transaksi di mode demo/staging, silakan merujuk ke panduan berikut: + +[Simulasi pembayaran Transfer Bank \- Virtual Account](https://docs.oyindonesia.com/#bank-transfer-virtual-account-payment-methods) + +[Simulasi pembayaran Transfer Bank \- Kode Unik](https://docs.oyindonesia.com/#bank-transfer-unique-code-payment-methods) + +Simulasi pembayaran QRIS (saat ini belum tersedia) + +[Simulasi pembayaran E-Wallet](https://docs.oyindonesia.com/#e-wallet-payment-methods) + +[Simulasi pembayaran Kartu Debit/Kredit](https://docs.oyindonesia.com/#cards-payment-methods-payment-methods) + +#### Skema dengan *Interface* +Setelah Anda berhasil membuat transaksi Routing Pembayaran menggunakan skema dengan *interface*, Anda dapat membagikan Link Pembayaran kepada pelanggan Anda. + +Langkah-langkah bagi pelanggan untuk menyelesaikan transaksi menggunakan skema dengan *interface* sama seperti menyelesaikan transaksi Link Pembayaran. Silakan merujuk ke panduan [berikut](https://docs.oyindonesia.com/#completing-payments-payment-link). + +### Memeriksa Status Transaksi +Semua transaksi Routing Pembayaran yang telah dibuat akan ditampilkan di dashboard. Anda dapat pergi ke menu "Routing Pembayaran" untuk melihat daftar transaksi yang telah dibuat. Di dalam dashboard, Anda dapat melihat detail transaksi, termasuk informasi transaksi yang dimasukkan saat pembuatan, status transaksi, dan nomor referensi pembayaran\* + +Dashboard juga memiliki fitur untuk mencari, memfilter, dan mengekspor daftar transaksi dalam berbagai format, seperti Excel (.xlsx), PDF (.pdf), dan CSV (.csv). + +Jika Anda tidak menerima *callback* transaksi dari sistem kami, Anda dapat menggunakan [API Check Status](https://api-docs.oyindonesia.com/#check-status-payment-routing-transaction-payment-routing) untuk mendapatkan status transaksi terbaru. + +**Catatan:** Nomor Referensi Pembayaran adalah nomor identifikasi untuk pembayaran yang berhasil ketika pelanggan menyelesaikan transaksi QRIS. Nomor referensi ini juga ditampilkan pada bukti transaksi pelanggan. Fitur ini hanya tersedia untuk transaksi QRIS. + +### Menerima Dana ke Saldo +Setelah pelanggan melakukan pembayaran, OY\! akan memperbarui status transaksi dan mengirimkan notifikasi ke sistem Anda untuk mengonfirmasi bahwa transaksi telah dibayar. OY\! kemudian akan menyelesaikan dana ke saldo akun OY\! Anda. + +Namun, waktu penyelesaian (*settlement*) dapat berbeda tergantung pada metode pembayaran yang digunakan, dengan rentang waktu mulai dari *real-time* hingga H+2 hari kerja. + +### Mengirim Dana ke Penerima +Routing Pembayaran memungkinkan Anda untuk meneruskan dana secara otomatis setelah transaksi dibayarkan oleh pelanggan. OY\! secara otomatis mengirim dana ke penerima yang telah ditentukan dalam proses pembuatan transaksi setelah pembayaran diterima. Anda perlu memastikan bahwa saldo Anda mencukupi untuk melakukan proses pengiriman dana, terutama untuk metode pembayaran yang memiliki *settlement non-real time*; jika tidak, proses pengiriman dana akan gagal karena saldo tidak mencukupi. + +## QRIS Aggregator +Quick Response Code Indonesian Standard (QRIS) adalah standar pembayaran QR di Indonesia yang dikembangkan oleh Bank Indonesia. Pembayaran dilakukan oleh pelanggan dengan memindai QR melalui aplikasi m-banking atau E-wallet mereka. Pembayaran QR sangat cocok untuk transaksi bernilai kecil karena menawarkan biaya yang terjangkau (0,7% per transaksi). QRIS Aggregator dapat digunakan untuk membuat transaksi QRIS sebagai metode pembayaran untuk kemudian ditampilkan ke customer Anda. + +**Catatan**: QRIS Aggregator menggunakan API Payment Routing yang sudah ada. Bagian ini hanya menjelaskan fitur, alur, dll. yang berhubungan dengan transaksi QRIS Aggregator. Semua transaksi akan ditampilkan di halaman dashboard OY! di bagian halaman Payment Routing. + +### Alur QRIS Aggregator +![QRIS Aggregator Scheme](../images/acceptingPayments/qris-aggregator/qris-flow.webp) + +### Fitur QRIS Aggregator +Jumlah maksimum per transaksi untuk QRIS adalah Rp10.000.000, sedangkan jumlah minimum per transaksi adalah Rp10.000, baik melalui Link Pembayaran maupun Routing Pembayaran. Jika Anda ingin menerima pembayaran di bawah Rp10.000, silakan hubungi perwakilan bisnis kami. + +### Cara Mengaktifkan +Ikuti langkah-langkah berikut untuk melakukan registrasi dan melakukan transaksi QRIS Aggregator: + +1. Buat akun di OY! +1. Lakukan verifikasi akun dengan mengisi formulir verifikasi. Pastikan untuk mencentang produk Terima Uang dan Kirim Uang, karena Routing Pembayaran termasuk dalam produk ini. +1. Tim OY! akan meninjau dan memverifikasi formulir serta dokumen yang dikirimkan. +1. Setelah verifikasi disetujui, atur informasi rekening penerima. + Catatan: Pastikan informasi rekening bank penerima benar, karena Anda hanya dapat mengaturnya sekali saja melalui dashboard demi alasan keamanan. +1. Buka halaman [Metode Pembayaran QRIS](https://docs.oyindonesia.com/#qr-code-qris-payment-methods) untuk informasi lebih detail mengenai apa yang diperlukan untuk aktivasi +1. Kirimkan alamat IP dan URL Callback ke perwakilan bisnis Anda atau melalui email ke business.support@oyindonesia.com. +1. OY! akan mengirimkan Production API Key melalui perwakilan bisnis Anda. + Catatan: Key Staging/Demo API dapat diakses melalui dashboard dengan masuk ke mode “Demo”, lalu temukan API key di menu kiri bawah. +1. Integrasikan QRIS Aggregator melalui API Payment Routing ke dalam sistem Anda. Ikuti panduan di dokumentasi [QRIS Aggregator - API Docs](https://api-docs.oyindonesia.com/#qris-aggregator). + +### Cara Membuat Transaksi QRIS Aggregator +Setelah Anda berhasil menyelesaikan proses registrasi untuk metode pembayaran QRIS, Anda dapat langsung membuat transaksi QRIS Aggregator melalui Payment Routing (hanya melalui API). Anda akan menggunakan Payment Routing Tanpa UI saat menerapkan skema QRIS Aggregator. + +1. Integrasikan API QRIS Aggregator [API QRIS Aggregator](https://api-docs.oyindonesia.com/#qris-aggregator) melalui Payment Routing. +1. Panggil [API Create QRIS transaction](https://api-docs.oyindonesia.com/#create-qris-transaction-qris-aggregator) milik OY!. + 1. Masukkan parameter “need_frontend” dengan nilai "false". + 1. Pilih QRIS sebagai metode pembayaran dengan + 1. Mengisi QRIS dalam parameter list_enable_payment_method. + 1. Mengisi QRIS dalam parameter list_enable_sof. +1. OY! akan mengembalikan URL untuk mengakses kode QR guna menyelesaikan pembayaran. +1. Tampilkan kode QR tersebut kepada pelanggan Anda di dalam aplikasi. + +### Cara Menyelesaikan Pembayaran +Silakan merujuk pada bagian [berikut ini](https://docs.oyindonesia.com/#qr-code-qris-payment-methods). + +### Memeriksa Status Transaksi +Semua transaksi QRIS Aggregator yang telah dibuat akan ditampilkan di dashboard. Anda dapat pergi ke menu "Routing Pembayaran" untuk melihat daftar transaksi yang telah dibuat. Di dalam dashboard, Anda dapat melihat detail transaksi, termasuk informasi transaksi yang dimasukkan saat pembuatan, status transaksi, dan nomor referensi pembayaran* +Dashboard juga memiliki fitur untuk mencari, memfilter, dan mengekspor daftar transaksi dalam berbagai format, seperti Excel (.xlsx), PDF (.pdf), dan CSV (.csv). + +Jika Anda tidak menerima callback transaksi dari sistem kami, Anda dapat menggunakan API Check Status untuk mendapatkan status transaksi terbaru. + [Check Status QRIS Transaction - API Docs](https://api-docs.oyindonesia.com/#check-status-qris-transaction-qris-aggregator). + +*Nomor Referensi Pembayaran adalah nomor identifikasi untuk pembayaran yang berhasil ketika pelanggan menyelesaikan transaksi QRIS. Nomor referensi ini juga ditampilkan pada bukti transaksi pelanggan. + +### Menerima Dana ke Saldo +Setelah pelanggan melakukan pembayaran, OY! akan memperbarui status transaksi dan mengirimkan notifikasi ke sistem Anda untuk mengkonfirmasi bahwa transaksi telah dibayar. OY! kemudian akan menyelesaikan dana ke saldo akun OY! Anda. Namun, waktu penyelesaian (settlement) dapat berbeda tergantung pada metode pembayaran yang digunakan, dengan rentang waktu mulai dari real-time hingga H+2 hari kerja. + +## API Account Linking +Tautan Pembayaran atau *Account Linking* adalah fitur yang dapat membantu pelanggan Anda untuk menautkan akun pembayarannya ke sistem Anda melalui proses *tokenization.* Dengan menautkan akun, pelanggan Anda dapat melihat saldo akun mereka di dalam aplikasi Anda dan nantinya dapat melakukan pembayaran tanpa diminta untuk memasukkan rincian kartu atau nomor E-wallet. Untuk saat ini, fitur ini mendukung E-wallet ShopeePay dan DANA. Fitur ini gratis tanpa biaya sepeser pun. + +### Alur Account Linking + +![Account Linking Flow](images/acceptingPayments/api-account-linking/payment-flow-api-account-linking.webp) +![Get Account Balance Flow](images/acceptingPayments/api-account-linking/payment-flow-api-get-e-wallet-balance.webp) +![Account Unlinking API Flow](images/acceptingPayments/api-account-linking/payment-flow-unlink-account-via-api-accoung-linking.webp) +![Account Unlinking via App Flow](images/acceptingPayments/api-account-linking/payment-flow-unlink-account-via-e-wallet-app.webp) + +### Cara mengaktifkan fitur Account Linking +Ikuti langkah-langkah berikut untuk mengaktifkan fitur Account Linking: + +1. Buat akun OY\! Indonesia. +2. Verifikasi akun Anda dengan cara mengisi formulir verifikasi. Pastikan Anda menceklis produk “Terima Uang”, karena fitur ini diperuntukkan untuk produk Terima Uang +3. Tim kami akan mereview formulir dan dokumen Anda. +4. Setelah akun Anda terverifikasi, siapkan informasi rekening bank penerima sebagai rekening penerima saldo OY\!. + **Catatan penting:** pastikan informasi rekening bank penerima sudah benar, karena tidak dapat diubah melalui dashboard OY\! +5. Kirimkan alamat IP, *callback* URL, dan *redirect* URL Anda kepada tim kami atau ke email [business.support@oyindonesia.com](mailto:business.support@oyindonesia.com). +6. OY\! Akan mengirimkan API Key Production sebagai otorisasi API melalui tim kami. + **Catatan:** API Key Staging/Demo dapat diakses melalui Dashboard OY\! dengan pergi ke mode "Demo," selanjutnya key Anda dapat ditemukan di menu kiri bawah. +7. Integrasikan API Account Linking ke sistem Anda. Silakan ikuti panduan yang ada di API Documentation di [halaman berikut](https://api-docs.oyindonesia.com/#api-account-linking). + +### Menautkan akun pembayaran pelanggan ke aplikasi Anda +Pelanggan Anda dapat menautkan akun pembayaran mereka ke aplikasi Anda dengan meng-*hit* API Account Linking. Ikuti langkah-langkah berikut untuk melakukannya : + +1. Integrasikan API Account Linking ke sistem Anda. Untuk lebih jelasnya, silakan buka halaman [berikut](https://api-docs.oyindonesia.com/#get-linking-url-api-account-linking). +2. *Hit* API Account Linking. Sebagai respons, Anda akan menerima URL *linking.* URL ini akan digunakan pelanggan Anda untuk memberikan izin permintaan penautan akun. +3. Pelanggan Anda memberikan izin dan memasukkan PIN yang diperlukan untuk mengotorisasi permintaan. +4. Penyedia pembayaran akan memproses permintaan, dan OY\! akan mengirimkan *callback* kepada Anda untuk memberitahu bahwa akun telah berhasil ditautkan. +5. Setelah akun berhasil ditautkan, pelanggan akan diarahkan kembali ke *redirect* URL yang Anda tentukan saat proses aktivasi fitur. + +### Melihat informasi saldo pelanggan Anda +Setelah pelanggan menautkan akun pembayaran mereka, Anda dapat mendapatkan informasi saldo akun pelanggan dengan menggunakan API *Get E-Wallet balance*. Anda dapat menampilkan saldo tersebut di dalam aplikasi Anda. Misalnya, menampilkan saldo selama proses *checkout*, sehingga pelanggan dapat mengetahui saldo mereka sebelum memilih metode pembayaran. Anda mendapatkan informasi lebih lanjut mengenai API *Get E-Wallet balance* di halaman [berikut](https://api-docs.oyindonesia.com/#get-e-wallet-account-balance-api-account-linking). + +### Melepas tautan pembayaran pelanggan dari aplikasi Anda +Pelanggan yang telah menautkan akun pembayaran mereka dapat melepasnya kapan saja. Mereka dapat melakukannya melalui API *Account Unlinking* atau melalui aplikasi penyedia pembayaran. Menggunakan API *Account Unlinking* memungkinkan pelanggan Anda untuk memutuskan tautan akun di dalam aplikasi Anda. Pilihan lain yang dapat dilakukan pelanggan adalah melepas tautan akun melalui aplikasi penyedia pembayaran. + +Ikuti langkah-langkah berikut untuk memandu Anda dan pelanggan Anda saat melepas tautan akun: + +**API Account Unlinking** + +1. Integrasikan API *Account Unlinking* ke sistem Anda. Lihat selengkapnya di [API Docs](https://api-docs.oyindonesia.com/#api-account-linking). +2. *Hit* API *Account Unlinking* OY\!. Setelah Anda *hit* API kami, OY\! akan meng-*hit* sistem penyedia pembayaran untuk melepas tautan akun pelanggan. +3. OY\! akan mengirimkan *callback* untuk memberi tahu Anda bahwa pelepasan tautan akun berhasil. + + +**Aplikasi Penyedia Pembayaran** + +1. ShopeePay + 1. Pelanggan Anda membuka aplikasi ShopeePay + 2. Pada menu, pilih OY\! sebagai aplikasi yang terhubung + 3. Pada bawah halaman “**Rincian Layanan**” , klik “**Hentikan Terhubung ke Layanan**” + +2. DANA + 1. Pelanggan Anda membuka aplikasi DANA + 2. Pilih Akun → **Akun Terhubung** + 3. Klik “**Lihat Lebih**” pada OY\! + 4. Checklist salah satu atau semua perangkat yang terhubung + 5. Pilih “**Hapus Semua**” + + + + diff --git a/source/includes/id/_business-app.md b/source/includes/id/_business-app.md new file mode 100644 index 00000000000..d245f57b69a --- /dev/null +++ b/source/includes/id/_business-app.md @@ -0,0 +1,99 @@ +# OY! Business App + +## OY! Business App + +Great news for you who always in mobility mode but you need to access our dashboard! Now you can access your OY! dashboard from the tip of your finger. OY! Business offers you with easy access to OY! Dashboard, so you can do your financial activities everywhere you are, without opening your laptop or PC. In this app, you can see your balance, account statement, transaction status, send money and also receive money. Yes, doing transaction is now easier than before! + +### Register and KYB + +1. Open your OY! Business app in your Android phone. +1. If it is your first time opening this application, you have to input your phone number. +1. Then, app will shows list of accounts that are tied to the phone number you entered before. +1. If you want to create a new account, tap “Buat Sekarang”. Then follow the instruction. +1. Once you success registering your new account, you can also submit your KYB. + +### Login + +1. Open your OY! Business app in your Android phone. +1. If it is your first time opening this application, you will need to input your phone number to verify your account. + +![OY! Business App input phone number](../images/oy_bisnis_app_input_phone_number.jpg) + +1. Next, select which method for sending OTP. Make sure to input the right phone number. Then, please input the OTP number we just sent you. + +![OY! Business App input OTP](../images/oy_bisnis_app_otp.jpg) + +1. If the phone number is not yet registed in OY!, you will be required to create a new account. + +![OY! Business App register page](../images/oy_bisnis_app_register.jpg) + +1. However, if your phone number have been registered to OY! before, the app will displays list of accounts that are tied to the phone number you entered before. + +![OY! Business App select account page](../images/oy_bisnis_app_account.jpg) + +1. Select an account you want to log in to. +1. First time logging in, you will be required to create a new PIN. This PIN will be used to log in to that account in the business app environment. +1. Once you have successfully logged in, you will be directed to homepage. Here, you can see your balance and see your latest transaction. + +![OY! Business App Homepage](../images/oy_bisnis_app_new_homepage_done_kyb.png) + + +### Create Payment Link + +1. In the homepage tap “Transaksi Sekarang” button. + +![OY! Business App Homepage](../images/oy_bisnis_app_new_homepage_done_kyb.png) + +1. Then, select “Tagih Uang”. + +![OY! Business App Select Transaction](../images/oy_bisnis_app_select_transaction.png) + +1. You will see your history of payment links and its transaction history. Then, tap “Buat Link Pembayaran” button in the bottom right. + +![OY! Business App Create a Payment link](../images/oy_bisnis_app_buat_link_pembayaran.jpg) + +1. Configure the payment link you want to create and fill all the required details. In this page, you can set the amount method (closed or open amount), admin fee method, payment link expiry date, and payment method(s) you want to provide to your customer(s). Them klik "Simpan". + +![OY! Business App Payment link Configuration](../images/oy_bisnis_app_konfigurasi_link_pembayaran.jpg) + +1. Define the amount (if you select closed amount in the configuration page), description, and the transaction ID. Then, tap “Buat Link Pembayaran” button. + +![OY! Business App Submit Payment link](../images/oy_bisnis_app_submit_buat_pembayaran.jpg) + + +1. Payment Link has been created and now you can share the link to your customer to receive payment from them. + + +### Send Money (Bulk Disbursement) + +1. In the homepage tap “Transaksi Sekarang” button. + +![OY! Business App Homepage](../images/oy_bisnis_app_new_homepage_done_kyb.png) + +1. Then, select “Kirim Uang”. + +![OY! Business App Select Transaction](../images/oy_bisnis_app_select_transaction.png) + +1. You will see your bulk disbursement campaign history. Then, tap “Buat Disbursement” button in the bottom right. + +![OY! Business App Create Disbursement](../images/oy_bisnis_create_disbursement.jpg) + +1. Create your Bulk Disbursement campaign. First, fill out your campaign details. + +![OY! Business App Campaign Detail](../images/oy_bisnis_dibsursement_campaign_detail.jpg) + +1. Next, fill the recipient data (bank, bank account number, amount, recipient email, phone number, note). + +![OY! Business App Recipient Detail](../images/oy_bisnis_disbursement_campaign_name.jpg) + +1. Next, check on your recipient list. If you want to add more recipient, click on "Tambah Transaksi" button. + +![OY! Business App Recipient List](../images/oy_bisnis_disbursement_recipient_list.jpg) + +1. Confirm your bulk disursement campaign. In this page you can ensure your campaign detail and your recipient data are correct before submitting the campaign. + +![OY! Business App Detail Campaign](../images/oy_bisnis_dibsursement_detail_campaign.jpg) + +1. Your Bulk Disbursement campaign has been created and will be displayed on the Bulk Disbursement campaign history page. You may wait for your approver to approve the bulk disbursement so we can execute the transaction. + +![OY! Business App Disbursement Campaign History](../images/oy_bisnis_disbursement_campaign_approve.jpg) diff --git a/source/includes/id/_expense-management.md b/source/includes/id/_expense-management.md new file mode 100644 index 00000000000..4546ad6ed1f --- /dev/null +++ b/source/includes/id/_expense-management.md @@ -0,0 +1,359 @@ +# Expense Management + +## Corporate Card +OY! Corporate Card product provides the offer to create customized virtual corporate cards that can be used to manage online transactions (e.g. software subscriptions, corporate travel expenses, purchase of supplies, etc.) without hassle. Virtual Corporate Card can be created through the OY! dashboard, therefore no technical integration is required to use this product. Please contact our business representative for further details about this feature. + +### Key Features for Virtual Corporate Card +Feature | Description +------ | ----------- +**Card creation** | You can use the funds directly from your OY! balance for corporate card needs. It is essential to top-up your OY! balance according to your desired card limit. +**Card control** | Create and control the card based on your requirements. You can set the limit amount (in Rupiah), validity period, card renewal frequency and even transaction limitations directly through OY! dashboard. Moreover, you can block and deactivate the card in real-time! Everything on your fingertips. +**Real-time transaction** | Transactions can be tracked easily through OY! dashboard and card holder’s page in real-time. There is no need to wait until the end of month for a full transaction statement. +**Analytics dashboard** | All transactions are recorded and reported in the analytics dashboard, helping companies easily manage and monitor their budgets. + +### Registration and Set Up +Follow the below check-list to ensure you're all set up to use the service: +1. Create an account for OY! business +1. Upgrade your account by submitting the required documentations +1. Have your upgrade request approved +1. Set up your receiving bank account information (note: ensure that the receiving bank account information is accurate as it cannot be changed via OY! dashboard for security reasons) +1. Once your account is approved, you can start using Virtual Corporate Card product + +### Testing +1. Log in to your OY! Dashboard. +1. If you haven’t set approver, please follow steps in How to Set Approver for Virtual Card +1. Select the "Demo" environment. +1. Navigate to "Corporate Card" product under the Expense Management menu. +1. Create a virtual corporate card by follow steps on How to Create Virtual Corporate Card +1. Your approver will receive email regarding approval request to create virtual corporate card, ask them to approve or reject the card for testing purposes +1. After your approver approves the request, the registered cardholder will receive email regarding dummy card information and virtual card status data on the demo OY! dashboard will be updated + +### How to Create Virtual Corporate Card +You can create new virtual corporate card by following these steps: +1. Log in to your OY! dashboard +1. Click “Corporate Card” under Expense Management menu +1. Click “Add New Card” +1. Choose "Virtual" for "Card Type" and usage frequency either single usage or multiple usage and click “Next” +1. Fill in Cardholder details and Card details +1. Once submitted, virtual card will be in “waiting for approval” state +1. After the approval step, the virtual card is ready to be used for transactions. + +**Notes:** Once your OY! balance is transferred to a virtual corporate card, it can only be used for virtual card transactions. + +* Corporate Card Dashboard + + ![Corporate Card Dashboard](../images/virtualCard/corporate_card_dashboard.png) + +* Virtual Card Type + + ![Virtual Card Type](../images/virtualCard/virtual_card_type.png) + +* Virtual Card Form + +![Virtual Card Form 1](../images/virtualCard/vcc_form_1.png) + +![Virtual Card Form 2](../images/virtualCard/vcc_form_2.png) + +### How to Transact with Virtual Card +Steps to use virtual card for online transaction: +1. Access your card information (including remaining balance & transaction) via email and enter OTP sent to the phone number registered. +1. Once accessed, input all of your card information into merchant side under “Credit / Debit Card” Option +1. Input 16 digit number, expiry date (MM/YY) and CVV +1. Submit the information and proceed with the transaction and the transaction should be successful. +1. For record purposes, you can upload the invoice for each transaction inside OY! dashboard. + +* Virtual Card Information + +![Virtual Card Information](../images/virtualCard/virtual_card_info.png) + +* Virtual Card Transaction Details + +![Virtual Card Transaction Details - Cardholder Page](../images/virtualCard/vcc_transaction_details.png) + +![Virtual Card Transaction Details - Email](../images/virtualCard/vcc_trx_email.png) + + +**Virtual Card Status** +Status| Description +------ | ----------- +Pending Approval | Card has been requested but not yet approved. Requests are valid for 14 days. +Active | Card is ready to be used for transactions. +Active with Warning | Card is active with balance, but only <15% balance remaining. +Inactive | Card has been blocked. The card can be activated any time needed through OY! dashboard. +Need top-up | New card has been created but failed to top-up the card balance due to insufficient OY! balance, OR current card limit is 0 and passed renewal time due to insufficient OY! balance. +Expired | Card is expired or intentionally archived permanently. +Rejected | Card is rejected by approver. + + +**Transaction Status** +Transaction Status | Description +------ | ----------- +Successful | Transaction was successful. +Failed | Failed transaction issue that related to OY! balance (top-up card or create card). +Reversal | Transaction was canceled, and the amount was refunded due to errors, returns, or fraud. +Declined | Transaction was declined by the merchant. +Refund | Refund by merchant. + +### How to Set Approver for Virtual Card +1. Log in to your OY! dashboard +1. Click “Corporate Card” under Expense Management menu +1. During first time product activation, you are required to fill in approver data +1. Fill in the approver details +1. You are required to review and check the T&C, then confirm your approver details +1. Approver will receive confirmation email + +* Add New Approver + +![Add New Approver](../images/virtualCard/add_new_approver.png) + +* Approver Form + +![Add Approver Form](../images/virtualCard/add_approver_form.png) + +**Notes:** Approver data cannot be added or edited through OY! dashboard for security purposes. Please contact our business representative for help. + +Parameter | Description +------ | ----------- +Name | Approver Name +Position | Approver Role +Phone Number | Approver Phone Number +Email | Approver email for card approval purposes + +### How to Monitor and Manage Virtual Cards +1. Log in to your OY! dashboard +1. Click “Corporate Card” under Expense Management menu +1. Click “See All Cards” +1. Dashboard will show analytics dashboard (divided per department) and list of card to manage +1. Click the card that needs to be managed + +![Virtual Card List](../images/virtualCard/vcc_card_list.png) + +**Card Actions** +Card Actions | Description +------ | ----------- +Resend Card Info | To resend card info to cardholders, in case of missing email. +Edit Information | To edit the card limit. Editing card limits will lead to card temporary blockage and require reapproval flow again. +Block | To temporarily lock the card, limit remains in the card and card’s status will be “Inactive” +Archive | To permanently lock the card, card limit will be reduced to 0 and remaining card limit will be returned to OY! balance. +Renew Limit | To renew the card limit with a desired amount using OY! balance. +Resend Approval Notification | To remind approver to approve the card request in case of missing email. +Delete | Only applicable for "Pending Approval" card. This will archive the card so the card is no longer used. + +### How to Set Up Card Configuration +1. Log in to your OY! dashboard +1. Click “Corporate Card” under Expense Management menu +1. Click “Corporate Card Configuration” +1. Select Department / Category / Approver +1. You can choose to whether add new, edit existing or delete +1. Click "Save Changes" + +* Department page prior to “Edit Department” button + +![Department List](../images/virtualCard/vcc_department_list.png) + +![Edit Department](../images/virtualCard/vcc_edit_department.png) + +* Category page + +![Category List](../images/virtualCard/vcc_category_list.png) + +![Edit Category](../images/virtualCard/vcc_edit_category.png) + +* Approver page + +![Approver List](../images/virtualCard/vcc_approver_list.png) + +### Decline Transaction Possible Reasons +Issues | Explanations +------ | ----------- +Card utilization is more than requested | Admin requests a card for single use only, but it is being used for more than one transaction. Please request a multiple use card if you expect the card to process multiple transactions. +Insufficient balance on the card | The balance on the card is less than the transaction amount. In this case, the cardholder may need to ask the Admin to top up the card. For example: The card balance is Rp 300,000 but the transaction amount is Rp 302,000. Since the card balance is less than the transaction amount, the transaction will not be processed successfully +Card is inactive | The card is temporarily blocked by the Admin. Cardholder needs to ask the Admin to activate the card. +Card is expired | The virtual card is no longer valid because it has passed its expiration date. Admin or cardholder can check the expired date from dashboard or virtual card information page. +Invalid card number | Cardholder entered the card number incorrectly. Please input the 16 digit card number correctly. +Invalid expiry date | Cardholder made an error entering the card expiry date. Please enter the correct expiry date. +Invalid CVV | Cardholder made an error inputting the CVV number. Please enter the correct CVV. +Issuer network not supported | Not all overseas merchants can process transactions for certain reasons. If you experience a declined error, please check the merchant's capabilities; they might only accept physical cards, regional restrictions, or other reasons. + +Notes +1. Transactions will be settled according to the bank’s instructions. +1. Successful card transactions will directly reduce card limit. +1. For refunds, please contact the merchant where you made the purchase. OY! is not responsible for processing refunds until we receive the funds back from the merchant. + 1. Refund duration will depend on the merchant and the bank. + 1. Once a refund has been issued, the balance will be returned back to your OY! balance. +1. It is your responsibility to block card usage if you notice any suspicious transactions. + + +## Reimbursement +OY! Reimbursement product offers an easy way to manage employee reimbursement requests and fund disbursements all in one platform. Employees can simply request reimbursement via the link sent to their email. No technical integration is required to utilize the product. + +### Key Features + +Feature | Description +------ | ----------- +Approval Capability | To ensure no fraudulent requests are made, a double approval mechanism exists in the product and is mandatory for the reimbursement process. The first layer is for the team manager via email, and the second layer applies to the admin via the dashboard. Our reimbursement product features a double approval mechanism to ensure integrity. +Disbursement Scheduling | Admin can also immediately schedule the disbursement time after approval from the team manager. Currently, the scheduled disbursement options are 1 day, 3 days, 7 days, and 14 days from the day of admin approval, allowing flexibility in managing cash flows. +Reimbursement Details | For admin, every reimbursement request from employees can be accessed through the OY! dashboard, including the uploaded file, to ensure it matches the requested amount. +Reimbursement Tracking | For employees, no more hassle in checking reimbursement progress with the admin. Your employee will receive a tracking email to check progress in real-time. + +**Notes:** +* First approval: Team Manager +* Second approval: Admin (it can be Finance Team or HR Team) + +This ensures that each approval request is reviewed by at least two reviewers, providing an extra layer of oversight and security. + +### Registration and Set Up +Follow the below check-list to ensure you're all set up to use the service: + +1. Create an account for OY! business +1. Upgrade your account by submitting the required documentations +1. Have your upgrade request approved +1. Set up your receiving bank account information (note: ensure that the receiving bank account information is accurate as it cannot be changed via OY! dashboard for security reasons) +1. Once your account is approved, you can start using Reimbursement product + +### Testing +1. Log in to your OY! Dashboard. +1. If you haven’t set approver for team manager, please follow steps in How to Set Approver +1. Select the "Demo" environment. +1. Navigate to "Reimbursement" product under the Expense Management menu. +1. Follow steps How to Distribute Reimbursement Link first to able open the reimbursement request page +1. Then create reimbursement request by follow the step How to Fill Reimbursement +1. Your reimbursement request will appear in Reimbursement table (in Demo environment) also sent to registered Team Manager’s email +1. Ask your registered Team Manager to open the link that was sent to their email and approve it for testing purposes. Your team manager can follow steps in How to Approve Transaction. +1. After your team manager approve or reject the transaction, it will reflect on your Demo OY! Dashboard. Afterward, as an admin you can approve or reject the transaction and schedule the disbursement as explained in How to Schedule Disbursement + +### How to Set Approver +By default, admin is the second approver for reimbursement requests. However, you need to register your Team Manager as the first approver for reimbursement requests. **Setting up the approver will only occur once when the page is first opened.** +1. Log in to your OY! dashboard. +1. Navigate to "Reimbursement" product under the Expense Management menu. +1. Click on "Create Reimbursement Link." +1. Choose "Register Approver." +1. Fill in the approver's name, email address, and department. +1. After registration, the approver will receive a notification via email. + +* Approver registration page + +![Approver registration](../images/reimbursement/Approver_Registration.png) + +**Notes** +* Approver emails are mapped based on department names, and duplicate department names are not allowed. +* After submission, addition, editing, or deletion of existing approvers can only be done via OY! Customer Service. +* Team managers will only receive notifications via email; no dashboard access is required. +* The approver list view is accessible in the dashboard under Reimbursement configuration. + +### How to Distribute Reimbursement Link +After Approver registration, you can start sharing the reimbursement link with employees through two methods: + +1. Via Bulk Upload: + 1. Download the sample file and input a list of employee emails in CSV or XLSX format. + 1. Upload the file for email distribution, then click “Submit”. + 1. Employees will receive the form link in their email and can use it to submit a reimbursement request. +1. Via Link Distribution + 1. Copy the link and distribute it using any convenient method. + 1. Employees may fill the form and proceed to submit a reimbursement request. + +* Bulk Upload and Link Distribution page from OY! Dashboard + +![Distribution page](../images/reimbursement/Reimbursement_Link.png) + +### How to Fill Reimbursement Request +1. Click reimbursement link that has been shared from Admin. +1. Fill the Employee Information and Reimbursement Request then click “Submit”. +1. You will receive confirmation email regarding reimbursement request has been submitted + +* Form Reimbursement Request page + +![Form Request page 1](../images/reimbursement/reimbursement_form_1.png) + +![Form Request page 2](../images/reimbursement/reimbursement_form_2_filled.png) + +**Mandatory Parameters in the Form** + +Parameter | Description +------ | ----------- +Employee Name | Employee identification purposes +Employee Email | This will be used to trigger tracking to employee post-submission +Department | Department will be mapped to approver's email directly +Bank Name | Disbursement bank name +Account Number | Disbursement bank account number. Bank account validity can be checked prior reimbursement submission +Item | Reimbursement item name or description +Amount | Reimbursement total amount (in IDR). Minimum Rp 20.000 +Upload File | Placeholder to upload invoice document. Max 2 file with PDF, JPG, & PNG format (Each file max 5MB). +Transaction Date | Date of transaction printed on the invoice + +**Notes:** you can resend the link anytime in case employees do not receive the email. + +### How to Approve Transaction (Reporting Manager) +1. When a new request is submitted by an employee, the respective team manager will receive a notification and an approver portal link via email. +1. Inside the link, the team manager can find all reimbursement requests with certain statuses (rejected, approved, and need approval). +1. The team manager can choose to either reject the request with a reason or simply approve. +1. Approving the request will trigger an update inside the OY! Dashboard and employee tracker page. + +* Approver portal (unique per approver) + +![Approver portal](../images/reimbursement/Approver_Portal_List.png) + +* Approver - request details with action buttons + +![Request details](../images/reimbursement/Approver_List_Detail.png) + +### How to Schedule Disbursement +Scheduled disbursement can only be done if the team manager has approved the request, and the OY! dashboard admin agrees to schedule the disbursement. + +1. Open the OY! Dashboard and check the Reimbursement transaction list. +1. Requests with "Need Approval" status mean that the team manager has approved the requests and will require further approval from the dashboard side. +1. OY! dashboard admin can either reject with a reason or approve with a scheduled disbursement day. + +* Request list in dashboard + +![Dashboard list](../images/reimbursement/Reimbursement_List.png) + + +* Request detail in dashboard + +![Request Detail](../images/reimbursement/Reimbursement_Detail1.png) + +![Request Detail](../images/reimbursement/Reimbursement_Detail2.png) + +**Note:** If there is insufficient balance on the day of scheduled disbursement, you can retry the fund transfer manually after a successful OY! balance top up. + + +**Dashboard Status** + +Status | Description +------ | ----------- +Pending Approval | Submitted by employee but no action yet from Team Manager. +Need Approval | Approved by approver but no action yet from OY! dashboard admin. +Canceled | Cancellation can only be performed by the employee. No further action needed. +Completed | Money has been successfully disbursed to employee’s bank account. +Rejected | Rejected by OY! dashboard admin or Team Manager. +Scheduled Payment | Request has been successfully approved, waiting for scheduled disbursement time. +Failed | Disbursement failed due to technical failures. +Insufficient Balance | Fail to disburse due to insufficient OY! balance (OY! dashboard admin can retry payment manually from dashboard after a successful top up of OY! balance). + +### How to Check Reimbursement Progress (Employee) +1. Employees can fill in the reimbursement request form portal via email. +1. Once submitted, the employee will receive a tracking email. +1. Inside the link, employees can find real-time reimbursement progress, from the submission timestamp until disbursement timestamp. +1. Employees can still cancel the request if the team manager has not yet approved. + +* Employee Tracker page + +![Employee tracker](../images/reimbursement/Employee_Tracker1.png) + +* Employee Tracker page -- Transaction Detail + +![Employee tracker](../images/reimbursement/Employee_Tracker2.png) + +**Tracker Status** + +Status | Description +------ | ----------- +Pending Payment | Request approved but money not yet received +Scheduled Payment | Request has been successfully approved, waiting for scheduled disbursement time. +Rejected | Rejected by admin or Team Manager +Canceled | Canceled by employee +Waiting Approval | Submitted but no action yet from Team Manager or admin +Completed | Money has been disbursed successfully + +**Note:** The tracking email is applicable to each employee per reimbursement request. \ No newline at end of file diff --git a/source/includes/id/_faq.md b/source/includes/id/_faq.md new file mode 100644 index 00000000000..fa1d87a1640 --- /dev/null +++ b/source/includes/id/_faq.md @@ -0,0 +1,142 @@ +# SSD (Soal Sering Ditanya) + +## API Kirim Uang & Multitransfer + +**Apa itu jadwal maintenance bank? apakah partner akan diinformasikan terkait itu?** + +Jadwal maintenance bank akan menghentikan semua transaksi ke bank terkait selama periode tertentu. Biasanya, tiap bank punya jadwal maintenance yang sudah dijadwalkan, yang tentunya berbeda antara satu bank dan bank lainnya. Agar transaksi Anda nyaman, kami akan membuat antrean transaksi yang diajukan saat jam pemeliharaan dan akan otomatis dikirimkan setelah maintenance selesai. + +**Berapa limit maksimal untuk mengirimkan uang?** + +Setiap e-wallet mempunyai limit maksimal masing-masing, berikut penjelasannya: + +| E-Wallet | Tipe Akun | Limit Maksimal | +| ------ | ------------- | -------------- | +| OVO | OVO Club | Rp 2.000.000 | +| OVO | OVO Premier | Rp 10.000.000 | +| DANA | DANA Verified | Rp 2.000.000 | +| DANA | DANA Premium | Rp 10.000.000 | +| GoPay | Unverified | Rp 2.000.000 | +| GoPay | Verified | Rp 10.000.000 | + +**Berapa limit minimal untuk mengirimkan uang?** + +Setiap e-wallet mempunyai limit minimal masing-masing, berikut penjelasannya: + + +| E-Wallet | Limit Minimal | +| ------- | -------------- | +| OVO | Rp 10.000 | +| DANA | Rp 10.000 | +| GoPay | Rp 10.000 | +| Linkaja | Rp 10.000 | + +**Apakah ada waktu Cut-Off time (COT) ?** + +Tidak ada. Kami beroperasi 24 jam, termasuk pada hari libur. + +**Apakah ada batasan volume transaksi dan nominal transaksi dalam sehari?** + +Tidak ada batasan harian untuk berapa banyak transaksi Multitransfer yang Anda buat. Tidak ada batasan juga untuk nominal transaksi transaksi Multitransfer. + +**Berapa banyak alamat email yang dapat saya kirimkan untuk notifikasi transaksi?** + +Anda dapat mengirimkan hingga 5 email per transaksi dengan batas maksimal 255 karakter. + +**Apakah saya bisa mengirimkan "catatan" yang ada di dalam mutasi rekening penerima?** + +Ya. Namun, kami hanya mendukung catatan untuk 7 bank ini: BCA, BNI, BRI, CIMB, DBS, Mandiri, dan Permata. Namun, harap diperhatikan bahwa jika terdapat gangguan saat kami menghubungkan ke bank yang disebutkan di atas, sistem kami tidak dapat mendukung agar catatan tersebut muncul dalam mutasi rekening bankpenerima. + +**Apakah saldo yang diterima dari API VA Aggregator dan produk Link Pembayaran serta Invoice dapat langsung digunakan untuk produk Kirim Uang?** + +Ya. Dana yang diterima dari API VA Aggregator dan produk Link Pembayaran serta Invoice akan secara otomatis masuk ke dalam saldo OY! Anda secara real-time, memungkinkan Anda untuk langsung menggunakan saldo tersebut untuk mengirimkan uang. + +**[Khusus Multitransfer] Bagaimana jika nama penerima di dokumen unduhan xlsx atau CSV berbeda dengan nama di rekening bank? Untuk apa nama & nomor telepon pelanggan digunakan?** + +Selama nomor rekening bank valid dan aktif, transaksi akan tetap dikirimkan. + +Nama dan nomor telepon hanya terlihat oleh partner dan digunakan untuk dokumentasi partner sendiri. Nama dan nomor telepon yang tercantum tidak akan digunakan oleh OY! + +**[Khusus API Kirim Uang] Apakah pengiriman uang dilakukan secara real-time?** + +Ya. Pencairan yang dilakukan melalui API Kirim Uang semuanya dilakukan secara real-time. + + +## API VA Aggregator + +Bank apa saja yang didukung untuk produk API VA Aggregator? + +Saat ini kami memiliki 8 bank yang tersedia untuk API VA Aggregator. Anda dapat melihat daftar bank [di sini](https://api-docs.oyindonesia.com/#va-aggregator-bank-code). + +**Apakah saldo yang saya terima langsung masuk secara real-time?** + +Ya, semua saldo yang Anda terima bersifat real-time dan akan segera tersedia masuk ke saldo OY! Anda. + +## Link Pembayaran/Invoice + +**Apa saja metode pembayaran yang tersedia untuk pelanggan saya?** + +Kami menyediakan berbagai metode pembayaran melalui transfer bank, e-wallet, kartu kredit/debit, dan kode QR dengan detail sebagai berikut: + +1. Transfer bank melalui Virtual Account: BCA, BNI, BRI, CIMB Niaga, Mandiri, dan Permata Bank. + +2. Transfer dengan kode unik: BCA + +3. Kartu kredit/debit: VISA, Mastercard + +4. E-Wallet: ShopeePay, DANA, LinkAja, OVO + +5. Kode QR: QRIS + +**Apa yang dimaksud dengan VA closed dan open amount? Apa yang terjadi jika nominal yang dibayarkan oleh pelanggan berbeda dari nominal yang tertera di Link Pembayaran yang saya buat?** + +Closed Amount adalah jenis Virtual Account yang hanya menerima pembayaran dengan nominal yang ditentukan. Pengguna tidak akan dapat membayar nominal selain dari nominal yang tertera. + +Open Amount adalah jenis Virtual Account VA yang dapat menerima pembayaran dengan nominal berapa pun. Jika pelanggan membayar nominal lebih dari yang tertera, link pembayaran akan tetap aktif. Link pembayaran akan berubah status menjadi SELESAI hanya ketika mencapai nominal TOTAL yang ditentukan. + +**Apa perbedaan antara transfer bank melalui Virtual Account dan transfer bank melalui kode unik?** + +Transfer bank melalui Virtual Account (VA) akan menghasilkan nomor rekening tujuan transfer yang khusus untuk setiap transaksi. Anda dapat membuat transaksi dengan jenis open amount atau closed menggunakan VA. Penjelasan rinci tentang VA dapat dilihat [di sini](https://docs.oyindonesia.com/#va-aggregator-accepting-payments). Anda dapat membuat transaksi VA melalui [API Routing Pembayaran](https://docs.oyindonesia.com/#va-aggregator-accepting-payments) atau [Link Pembayaran ](https://docs.oyindonesia.com/#va-aggregator-accepting-payments). + +Berbeda dari sebelumnya, transfer bank melalui kode unik menghasilkan kode unik untuk setiap transaksi, tetapi nomor rekening tujuan akan tetap sama. Total nominal yang dibayarkan akan dikurangi dengan nilai kode unik tersebut. Sebagai contoh, pelanggan Anda ingin membayar transaksi sebesar Rp100.000 dan mendapatkan Rp100 sebagai kode unik. Jadi, pelanggan Anda akan membayar total Rp 99.900 untuk menyelesaikan pembayaran. Kode Unik juga memiliki keterbatasan dibandingkan dengan VA, di mana Anda hanya dapat membuat transaksi kode unik selama jam operasional (03.00 pagi - 08.30 malam GMT+7). + + +## Routing Pembayaran + +**Apa saja metode pembayaran yang tersedia untuk pelanggan saya?** + +Kami menyediakan berbagai metode pembayaran melalui transfer bank, e-wallet, kartu kredit/debit, dan kode QR dengan detail sebagai berikut: + +1. Transfer bank melalui Virtual Account: BCA, BNI, BRI, CIMB Niaga, Mandiri, dan Permata Bank. + +2. Transfer dengan kode unik: BCA + +3. Kartu kredit/debit: VISA, Mastercard + +4. E-Wallet: ShopeePay, DANA, LinkAja, OVO + +5. Kode QR: QRIS + +**Apa perbedaan antara transfer bank melalui Virtual Account dan transfer bank melalui kode unik?** + +Transfer bank melalui Virtual Account (VA) akan menghasilkan nomor rekening tujuan transfer yang khusus untuk setiap transaksi. Anda dapat membuat transaksi dengan jenis open amount atau closed menggunakan VA. Penjelasan rinci tentang VA dapat dilihat [disini](https://docs.oyindonesia.com/). Anda dapat membuat transaksi VA melalui API Routing Pembayaran atau VA Aggregator. + +Berbeda dari sebelumnya, transfer bank melalui kode unik menghasilkan kode unik untuk setiap transaksi, tetapi nomor rekening tujuan akan tetap sama. Total nominal yang dibayarkan akan dikurangi dengan nilai kode unik tersebut. Sebagai contoh, pelanggan Anda ingin membayar transaksi sebesar Rp100.000 dan mendapatkan Rp100 sebagai kode unik. Jadi, pelanggan Anda akan membayar total Rp 99.900 untuk menyelesaikan pembayaran. Kode Unik juga memiliki keterbatasan dibandingkan dengan VA, di mana Anda hanya dapat membuat transaksi kode unik selama jam operasional (03.00 pagi - 08.30 malam GMT+7). Anda dapat membuat transaksi kode unik melalui [API Routing Pembayaran](https://docs.oyindonesia.com/#va-aggregator-accepting-payments) atau [Link Pembayaran ](https://docs.oyindonesia.com/#va-aggregator-accepting-payments). + + + +**Apa perbedaan antara E-Wallet Sekali Pakai dan E-Wallet Direct Payment?** + +Pembayaran E-wallet sekali pakai dapat membuat URL pembayaran yang bisa dibayar oleh siapa pun. Begitu URL pembayaran dibuka, pelanggan Anda akan diarahkan ke aplikasi E-wallet dan menyelesaikan pembayaran di dalam aplikasi E-wallet. + +Sementara, E-wallet direct payment dapat membuat URL pembayaran yang diperuntukkan untuk pengguna tertentu. Pelanggan Anda perlu menghubungkan akun E-wallet mereka terlebih dahulu dengan melakukan [Account Linking](https://docs.oyindonesia.com/#feature-account-linking-accepting-payments). Setelah pelanggan menghubungkan akun mereka dengan aplikasi Anda, maka otomatis pembayaran akan dapat dilakukan. OY! akan mengembalikan otentikasi kepada pelanggan untuk memasukkan PIN E-wallet dan menyelesaikan pembayaran. Dengan menggunakan direct payment, pelanggan Anda akan menyelesaikan transaksi di dalam aplikasi Anda sendiri. Berikut adalah detail pembayaran masing-masing E-wallet: + + +| | Sekali Pakai | Direct | +| --- | -------- | ------ | +| E-Wallet yang didukung | ShopeePay, LinkAja, DANA, OVO | ShopeePay | +|Harus menghubungkan akun terlebih dahulu? | Tidak | Ya | +| Dapat dibuat melalui.. | API Payment Routing
Payment Link
API E-Wallet Aggregator | API Payment Routing | +| Mengirimkan nomor HP saat permintaan pembuatan API. | Opsional | Wajib | +| Siapa yang dapat melakukan pembayaran? | Semua user/guest | User Tertentu| +| Pembayaran dilakukan di | Di aplikasi E-wallet | Di aplikasi Anda sendiri | diff --git a/source/includes/id/_home.md b/source/includes/id/_home.md new file mode 100644 index 00000000000..25abb6aa2fc --- /dev/null +++ b/source/includes/id/_home.md @@ -0,0 +1,141 @@ +# Beranda + +Selamat datang di OY! Indonesia. Kami bertujuan untuk menjadi perusahaan money movement terdepan yang selalu menempatkan partner kami sebagai prioritas utama. Berbekal berbagai produk, seperti Terima Uang dan Kirim Uang, kami siap membantu pertumbuhan bisnis Anda melalui solusi pembayaran yang cepat dan aman. + +## Temukan Manfaat OY! Sesuai Bisnis Anda + +### Sesuaikan Produk dengan Kebutuhan Bisnis Anda + +**Kirim Uang** +- Multitransfer +- API Kirim Uang +- Transfer Internasional +- Pembayaran Tagihan +- Pengembalian Dana + +**Terima Uang** +- Link Pembayaran +- Virtual Account Aggregator +- E-Wallet Aggregator +- Routing Pembayaran + +## Proses Pendaftaran + +Proses pendaftaran dimulai dengan membuat akun secara gratis. Anda dapat mencoba produk dan fitur kami di mode demo, di mana Anda dapat melakukan transaksi dummy (tanpa menggunakan uang sungguhan). + +Setelah berhasil membuat akun, jika Anda tertarik untuk melanjutkan menggunakan OY! untuk bisnis Anda, Anda diharuskan untuk memverifikasi bisnis Anda dengan mengirimkan pesyaratan dokumen yang diperlukan. + +### Cara Membuat Akun + +Untuk membuat akun, ikuti langkah-langkah berikut: + +1. **Daftar via website OY!:** Untuk membuat akun, silakan daftar [di sini](https://business.oyindonesia.com/register?), dan lengkapi formulir dengan semua informasi yang diperlukan. + + *Harap perhatikan bahwa username Anda tidak dapat diubah setelah formulir pendaftaran dikirimkan.* + +![Creating Account](../images/desktop_register.png) + +2. **Aktivasi akun Anda:** Setelah formulir pendaftaran dikirimkan, sebuah link aktivasi akan dikirimkan ke email yang terdaftar. Setelah Anda berhasil masuk, Anda dapat mencoba produk kami di mode demo, di mana Anda dapat melakukan transaksi tanpa menggunakan uang sungguhan. Jika Anda ingin bertransaksi di mode production atau mengintegrasikan platform Anda dengan OY!, silakan baca langkah-langkah di bawah ini untuk memverifikasi akun Anda. + +![Creating Account](../images/creating_account_2.jpg) + + Setelah Anda berhasil masuk, Anda akan dapat mencoba produk kami di mode demo, di mana Anda dapat melakukan transaksi tanpa menggunakan uang sungguhan. Jika Anda siap melanjutkan, lihat bagian di bawah untuk informasi lebih lanjut tentang langkah-langkah untuk meng-upgrade akun Anda. + +### Verifikasi Bisnis Anda + +Silakan ikuti langkah-langkah berikut untuk memverifikasi bisnis Anda: + +1. **Lakukan permintaan verifikasi bisnis:** Untuk mengakses semua produk OY!, silakan pilih "Verifikasi Bisnis" pada menu di sisi kiri dashboard OY! di mode Production dan lengkapi semua informasi yang diperlukan. + + ![Upgrade](../images/upgrade_account_1.png) + +2. **Untuk tipe bisnis individual:** Unggah KTP Anda untuk verifikasi identitas. Silakan ambil foto KTP dan foto selfie sambil memegang KTP. Sistem akan secara otomatis membaca informasi pada foto KTP Anda dan formulir otomatis terisi. Pastikan semua informasi yang tertera sesuai dengan KTP Anda. + + *Untuk tipe bisnis individual:* + ![Upgrade](../images/upgrade_account_2.png) + +3. **Untuk tipe bisnis perusahaan:** Unggah persyaratan dokumen. Kami akan meminta persyaratan dokumen yang berbeda untuk berbagai jenis entitas perusahaan. Setelah memilih jenis perusahaan Anda, kami akan memberitahu dokumen apa yang perlu Anda kirimkan. + + ![Upgrade](../images/upgrade_account_3.png) + + Silakan unggah dokumen yang diperlukan sesuai dengan tipe bisnis Anda. Dokumen yang diunggah harus dalam format PDF dan memiliki ukuran per dokumen kurang dari 10 MB. + + - Berikut adalah contoh dokumen yang harus diunggah perusahaan tipe bisnis penyelenggara jasa sistem pembayaran (PJSP) + ![Upgrade](../images/upgrade_account_4.png) + + - Berikut adalah contoh dokumen yang harus diunggah perusahaan tipe bisnis non-PJSP + ![Upgrade](../images/upgrade_account_5.png) + +4. **Isi Formulir Informasi Bisnis:** Berikan informasi lebih lanjut tentang bisnis Anda dengan mengisi formulir informasi. Harap perhatikan bahwa Anda tidak akan dapat mengubah informasi bisnis Anda ini dalam status Butuh Revisi. + + ![Upgrade](../images/upgrade_account_7.png) + +5. **Masukkan Nomor Rekening Bank Anda:** Tambahkan Informasi Rekening Bank sebagai rekening tujuan untuk menarik uang dari Platform OY! Business. + + *Catatan: informasi rekening bank penerima tidak dapat diubah melalui dashboard OY! setelah permintaan verifikasi bisnis Anda disetujui karena alasan keamanan. Silakan hubungi kami di partner@oyindonesia.com untuk mengubah informasi rekening bank penerima.* + + ![Upgrade](../images/upgrade_account_6.png) + +6. **Kirimkan Permintaan Verifikasi Anda:** Klik "Kirim", dan status "Menunggu Persetujuan" akan muncul di halaman utama, menandakan bahwa permintaan Anda telah diajukan. + + Proses ini akan membutuhkan waktu sekitar 1-2 hari kerja. Jika status permintaan Anda adalah "Butuh Revisi" atau "Ditolak", silakan lanjutkan membaca. Jika tidak, Anda sudah siap untuk melanjutkan. + +### Status: Butuh Revisi + +Setelah mengkaji permintaan verifikasi, tim kami mungkin meminta dokumen tambahan. Silakan ikuti langkah-langkah di bawah ini: + +1. **Baca laporan kami:** Laporan hasil verifikasi akan dikirimkan ke email Anda mengenai informasi dan dokumen mana saja yang perlu direvisi atau ditambahkan. + +2. **Isi ulang formulir:** Kami akan menunjukkan informasi dan dokumen mana yang perlu direvisi. Ketika mengajukan permintaan verifikasi bisnis lagi, harap kirimkan kembali semua pesyaratan dokumen Anda. Dokumen hanya diterima dalam format PDF dan harus berukuran kurang dari 10 MB. + +3. **Ajukan permintaan Anda:** Klik "Kirim", dan status baru "Menunggu Persetujuan" akan muncul di halaman ini, menandakan bahwa permintaan Anda telah diajukan. + + Harap perhatikan bahwa setelah 3 kali atau lebih proses revisi, Anda akan memerlukan waktu tunggu yang lebih lama untuk mengajukan permintaan verifikasi berikutnya. Kami akan memberi tahu Anda melalui email atau dashboard kapan Anda dapat melakukan pengajuan berikutnya. + + Untuk pertanyaan lebih lanjut, silakan hubungi kami di partner@oyindonesia.com, tim kami akan menghubungi Anda. + +### Status: Ditolak + +Untuk pertanyaan lebih lanjut mengenai status ini, silakan hubungi kami di partner@oyindonesia.com, tim kami akan menghubungi Anda. + +## Persyaratan Dokumen + +Berikut adalah daftar persyaratan dokumen yang dibutuhkan untuk verifikasi bisnis berdasarkan tipe bisnis Anda: + +- **Akun Individual** + - Kartu Tanda Penduduk (KTP) + +- **Akun Perusahaan Penyelenggara Jasa Sistem Pembayaran (PJSP)** + - Nomor Pokok Wajib Pajak (NPWP) Perusahaan + - Surat Izin Usaha Perdagangan (SIUP)/ Tanda Daftar Perusahaan (TDP) / Nomor Induk Berusaha (NIB) + - SK Kemenhukam + - Akta Perubahan Terakhir Terlegalisir + - Akta Pendirian Perseroan Teregalisir + - KTP Direktur + - Struktur Pemegang Saham + - Lisensi BI/OJK + - Pengesahan Kementerian Terkait + +- **Akun Perusahaan Non-PJSP** + - Nomor Pokok Wajib Pajak (NPWP) Perusahaan + - Surat Izin Usaha Perdagangan (SIUP)/ Tanda Daftar Perusahaan (TDP) / Nomor Induk Berusaha (NIB) + - SK Kemenhukam + - Akta Perubahan Terakhir Terlegalisir + - Akta Pendirian Perseroan Teregalisir + - KTP Direktur + - Struktur Pemegang Saham + - Pengesahan Kementerian Terkait + +## Mulai Bertransaksi + +Sebelum bertransaksi atau melakukan integrasi menggunakan fitur OY!, pastikan Anda sudah melakukan hal-hal berikut: + +1. Buat akun OY! Indonesia +2. Verifikasi bisnis Anda dengan mengunggah persyaratan dokumen +3. Tunggu hingga akun Anda terverifikasi +4. (Opsional) Jika Anda ingin menggunakan produk atau fitur API kami, silakan kirimkan IP dan URL callback (untuk production dan demo) ke perwakilan tim kami, atau ke partner@oyindonesia.com +5. (Opsional) Jika Anda ingin menggunakan produk atau fitur API kami, Anda perlu meminta API Key untuk production dan demo ke perwakilan tim kami. Anda tidak diwajibkan untuk verifikasi bisnis jika ingin meminta API Key untuk demo, namun jika Anda ingin meminta API Key untuk production, Anda perlu memverifikasi bisnis Anda. +6. (Opsional) Lakukan pengujian. Kami sarankan Anda untuk melakukan pengujian secara menyeluruh sebelum memulai bertransaksi. Kami telah menyediakan Mode demo di dashboard kami, di mana Anda dapat menguji transaksi untuk memahami bagaimana cara menggunakan produk kami tanpa menggunakan uang sungguhan. Jika Anda adalah seorang developer, Anda juga dapat menguji integrasi dengan platform Anda. +7. (Opsional) Jika bisnis atau perusahaan Anda ingin menambah pengguna atau sub-akun, Anda dapat menggunakan fitur “Sub-Entity” + +Selamat! Anda sudah dapat menikmati kemudahan bertransaksi menggunakan OY! Indonesia. diff --git a/source/includes/id/_oy-tutorial.md b/source/includes/id/_oy-tutorial.md new file mode 100644 index 00000000000..968b28cfa30 --- /dev/null +++ b/source/includes/id/_oy-tutorial.md @@ -0,0 +1,616 @@ +# Tutorial Dashboard OY! + +## Masuk ke Dashboard + +Untuk masuk ke dashboard OY!, lakukan langkah berikut: + +1. Akses halaman login di https://desktop-business.oyindonesia.com/login. +2. Masukkan username dan password Anda. + +![Login page](../images/desktop_login.png) + +3. Anda akan diarahkan untuk memasukkan kode OTP. Silakan cek inbox email yang terdaftar untuk mendapatkan kode OTP, lalu masukkan di kolom yang tersedia. + +![Login OTP](../images/desktop_login_otp.png) + +![Email OTP](../images/email_otp.png) + +Jika Anda mencentang opsi “Ingat perangkat ini”, Anda tidak perlu memasukkan OTP saat Anda masuk kembali. Oleh karena itu, langkah ketiga hanya diperlukan saat Anda masuk untuk pertama kali setelah Anda membuat akun. + +## Analitik Transaksi + +Fitur analitik pada dashboard OY! memberikan Anda informasi tentang transaksi pendapatan dan pengeluaran yang terjadi di OY!. Dengan fitur ini, Anda bisa tahu berapa banyak uang yang sudah Anda keluarkan dan berapa yang sudah Anda dapatkan dalam jangka waktu tertentu. Anda bisa melihat rincian transaksi Anda secara harian, mingguan, atau bulanan. Anda juga bebas memilih periode waktu yang Anda inginkan. Dari sini, Anda akan mendapatkan gambaran tentang bagaimana tren transaksi bisnis Anda bergerak. Dengan demikian, fitur ini dapat membantu Anda dalam mengambil keputusan bisnis baru guna mengembangkan bisnis Anda. + + +![Dashboard Analytics Image](../images/desktop_analytics_spend_earn.png) + +* Anda bisa pilih seberapa rinci dan dalam periode waktu berapa Anda ingin melihat informasi transaksi Anda. +* Indikator kenaikan dan penurunan akan membandingkan bagaimana transaksi Anda pada periode yang Anda pilih dengan periode sebelumnya. +* Jika Anda arahkan kursor ke suatu titik garis, jumlah volume transaksi akan muncul. + +Bagi Anda yang menggunakan produk Virtual Account, Link Pembayaran, dan E-wallet, Anda bisa melihat perbandingan antar metode pembayaran yang digunakan oleh pelanggan Anda. Informasi ini bisa memberikan wawasan penting bagi Anda agar tahu metode pembayaran mana yang paling banyak digunakan oleh pelanggan Anda. + + + +![Dashboard Analytics Payment Method](../images/desktop_analytics_paymethod.png) + +## Manajemen Akun + +Jika Anda memiliki peran Super Admin, Anda dapat menambahkan akun untuk anggota tim Anda dan menentukan peran untuk mereka. + +Berikut adalah langkah-langkah berikut untuk menambahkan pengguna baru: + +1. Masuk ke dashboard OY! +2. Klik menu Akun, kemudian klik “Manajemen Pengguna” +3. Klik tombol “Tambah Pengguna” di sebelah kanan atas + +![User Management 1](../images/user_management_1.png) + +4. Isi nama lengkap, username, email, nomor telepon (opsional), dan kata sandi + +![User Management 2](../images/user_management_2.png) + +5. Pilih peran yang ingin Anda berikan kepada anggota tim Anda. Anda dapat memilih antara Administrator, Approver, dan Maker + +Selain menambahkan pengguna baru, Super Admin juga dapat mengubah informaasi dan menghapus pengguna yang sudah ada dengan mengklik tombol ubah/hapus pada tabel Daftar Pengguna. + +Berikut adalah daftar kontrol akses di dashboard OY! berdasarkan peran: + + +| Akses | Superadmin | Admin | Approver | Maker +|-------|-------|-------|-------|-------| +|Verifikasi Akun| Bisa mengajukan| Bisa mengajukan| - | - | +|Analitik| Bisa melihat| Bisa melihat| Bisa melihat| Bisa melihat| +|Laporan Transaksi| Bisa melihat dan mengunduh | Bisa melihat dan mengunduh | Bisa melihat dan mengunduh | Bisa melihat dan mengunduh | +|Laporan Penyelesaian| Bisa melihat dan mengunduh | Bisa melihat dan mengunduh | Bisa melihat dan mengunduh | Bisa melihat dan mengunduh | +|Isi Saldo | Bisa mengisi saldo | Bisa mengisi saldo | Bisa mengisi saldo | Bisa mengisi saldo | +|Tarik Saldo | Bisa menarik saldo| Bisa menarik saldo| Bisa menarik saldo| Bisa menarik saldo| +|Tambah Pengguna | Bisa menambahkan | Bisa menambahkan | - | - | +|Profil Pengguna| Bisa melihat dan mengedit | Bisa melihat dan mengedit | Bisa melihat dan mengedit | Bisa melihat dan mengedit | +|Profil Bisnis| Bisa melihat dan mengedit | Bisa melihat dan mengedit | Bisa melihat dan mengedit | Bisa melihat dan mengedit | +|Authenticator OTP| Punya akses | Punya akses | Punya akses | Punya akses | +|Tambah Metode Pembayaran | Bisa menambahkan | Bisa menambahkan | - | - | +|Laporan Otomatis| Bisa mengatur | Bisa mengatur | - | - | +|Tarik Saldo Otomatis| Bisa mengatur | Bisa mengatur | - | - | +|Multi Approval| Bisa mengatur | Bisa mengatur | - | - | +|Pengaturan Notifikasi| Bisa mengatur | Bisa mengatur | - | - | +|Opsi Developer| Bisa mengatur | Bisa mengatur | - | - | +|Tambah Kategori Transaksi| Bisa menambahkan | Bisa menambahkan | - | - | +|Pengaturan Bukti Transaksi | Bisa mengatur | Bisa mengatur | Bisa mengatur | Bisa mengatur | + +Multi Entity Managementy + +| Akses | Superadmin | Admin | Approver | Maker +|-------|-------|-------|-------|-------| +|Membuat Sub-entit| Bisa membuat| Bisa membuat | - | - | +|Isi ulang saldo Sub-entity| Bisa mengisi saldo| Bisa mengisi saldo | - | - | +|Memutus Sub-entity| Bisa memutus | Bisa memutus | -| -| + +Produk Multitransfer + +| Akses | Superadmin | Admin | Approver | Maker +|-------|-------|-------|-------|-------| +|Buat transaksi | Bisa membuat| Bisa membuat | - | Bisa membuat | +|Menyetujui transaksi| Bisa menyetujui| Bisa menyetujui | Bisa menyetujui | - | +|Melihat dan mengunduh laporan transaksi| Bisa melihat dan mengunduh | Bisa melihat dan mengunduh | Bisa melihat dan mengunduh | Bisa melihat dan mengunduh | + +Produk Pengembalian Dana + +| Akses | Superadmin | Admin | Approver | Maker +|-------|-------|-------|-------|-------| +|Buat pengembalian dana | Bisa membuat| Bisa membuat | - | Bisa membuat | +|Menyetujui pengembalian dana| Bisa menyetujui| Bisa menyetujui | Bisa menyetujui | - | +|Melihat dan mengunduh laporan transaksi| Bisa melihat dan mengunduh | Bisa melihat dan mengunduh | Bisa melihat dan mengunduh | Bisa melihat dan mengunduh | + +Produk Invoice Pembelian + +| Akses | Superadmin | Admin | Approver | Maker +|-------|-------|-------|-------|-------| +|Buat invoice| Bisa membuat| Bisa membuat | - | Bisa membuat | +|Menyetujui invoice | Bisa menyetujui | Bisa menyetujui | Bisa menyetujui | - | +|Melihat dan mengunduh laporan invoice| Bisa melihat dan mengunduh | Bisa melihat dan mengunduh | Bisa melihat dan mengunduh | Bisa melihat dan mengunduh | + +Produk Link Pembayaran + +| Akses | Superadmin | Admin | Approver | Maker +|-------|-------|-------|-------|-------| +|Membuat Link Pembayaran| Bisa membuat| Bisa membuat | Bisa membuat | Bisa membuat | +|Melihat dan mengunduh laporan link pembayaran| Bisa melihat dan mengunduh | Bisa melihat dan mengunduh | Bisa melihat dan mengunduh | Bisa melihat dan mengunduh | + +Produk Virtual Account + +| Akses | Superadmin | Admin | Approver | Maker +|-------|-------|-------|-------|-------| +|Membuat VA via dashboard| Bisa membuat| Bisa membuat | Bisa membuat | Bisa membuat | +|Melihat dan mengunduh laporan VA| Bisa melihat dan mengunduh | Bisa melihat dan mengunduh | Bisa melihat dan mengunduh | Bisa melihat dan mengunduh | + +E-Wallet Aggregator + +| Akses | Superadmin | Admin | Approver | Maker +|-------|-------|-------|-------|-------| +|Melihat dan mengunduh laporan E-Wallet| Bisa melihat dan mengunduh | Bisa melihat dan mengunduh | Bisa melihat dan mengunduh | Bisa melihat dan mengunduh | + +Produk Invoice Penjualan + +| Akses | Superadmin | Admin | Approver | Maker +|-------|-------|-------|-------|-------| +|Buat invoice | Bisa membuat| Bisa membuat | Bisa membuat | Bisa membuat | +|Melihat dan mengunduh laporan invoice| Bisa melihat dan mengunduh | Bisa melihat dan mengunduh | Bisa melihat dan mengunduh | Bisa melihat dan mengunduh | + +Inquiry API + +| Akses | Superadmin | Admin | Approver | Maker +|-------|-------|-------|-------|-------| +|Melihat dan mengunduh laporan Inquiry API| Bisa melihat dan mengunduh | Bisa melihat dan mengunduh | Bisa melihat dan mengunduh | Bisa melihat dan mengunduh | + +API Transaksi Data + +| Akses | Superadmin | Admin | Approver | Maker +|-------|-------|-------|-------|-------| +|Melihat dan mengunduh laporan API Transaksi Data| Bisa melihat dan mengunduh | Bisa melihat dan mengunduh | Bisa melihat dan mengunduh | Bisa melihat dan mengunduh | + + +## 2-Factor Authentication + +Untuk meningkatkan keamanan akun Anda, Anda bisa mengaktifkan 2-Factor Authentication. Saat ini, OY menyediakan metode keamanan ini melalui aplikasi authenticator. Berikut adalah langkah-langkahnya: + +1. Masuk ke dashboard OY! +2. Pilih menu Akun -> Authenticator OTP +3. Unduh aplikasi authenticator di handphone Anda (dari Play Store/App Store) atau PC (Anda bisa unduh ekstensi untuk brower Anda). Contoh aplikasi Authenticator: Google Authenticator, Microsoft Authenticator, Authy +4. Scan QR code yang terlihat di dahsboard dengan aplikasi Authenticator Anda atau masukkan kunci pengaturan yang ada di sampingnya ke Aplikasi Authenticator Anda +5. Ketik 6 digit kode yang muncul di aplikasi Authenticator Anda ke dalam kolom “Authenticator OTP” + +## Mengisi Saldo OY! +Berikut adalah langkah-langkah untuk mengisi ulang saldo OY! Anda melalui dashboard: + +1. Masuk ke dashboard OY! +2. Pergi ke menu “Laporan Transaksi” lalu pilih “Laporan Transaksi Rekening” +3. Klik tombol “Bagaimana Cara Isi Ulang”. Ada 2 cara untuk menambah saldo akun OY! Anda: + +### Isi Ulang via Virtual Account +Anda bisa melihat nomor VA di tab 'Virtual Account' setelah Anda mengklik tombol 'Bagaimana Cara Isi Ulang'. Jumlah saldo yang Anda isi akan otomatis masuk ke akun Anda secara real-time tanpa perlu mengirim konfirmasi manual ke OY. + +![VA Topup](../images/desktop_topup.png) + +### Isi Ulang Manual via Transfer Bank +Selain menggunakan Virtual Account, Anda juga dapat mentransfer ke rekening giro OY. Jika Anda memilih untuk isi ulang saldo melalui metode ini, Anda perlu melakukan konfirmasi manual agar uang Anda dapat diteruskan ke akun Anda. + +Setelah Anda menyelesaikan transfer dana ke OY! Indonesia, Anda perlu melakukan langkah-langkah berikut: + +1. Klik tab “Transfer Bank Manual” pada menu Isi Ulang Saldo + +![Manual Topup](../images/manual_topup.png) + +2. Isi kolom informasi berikut: + +| Field Name | Description | +| --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Amount | The amount that you have topped up (as per written in the Bukti Transfer) | +| Beneficiary Bank | A dropdown where you can choose to which bank you have transferred the top up amount to | +| Transfer Receipt | Click the upload button to upload the Bukti Transfer obtained after you have successfully performed the transfer. Can be in PDF, PNG, or JPEG format, with max. file size 10 MB | +| Transfer Receipt Type | Transfer Receipt with Reference Number --> If your uploaded transfer receipt displays the reference number, you need to choose this option. Transfer Receipt without Reference Number --> If your uploaded transfer receipt doesn't display the reference number, you need to choose this option and fill in the date & timestamp according to your Bukti Transfer. | + +3. Klik “Kirim Sekarang” +4. Anda akan menerima email menyatakan bahwa isi ulang saldo Anda sedang diproses. + + +![Topup Confirmation Dashboard](../images/topup_confirmation_dashboard.png) + +![Topup Confirmation Email](../images/topup_confirmation_email.png) + +5. Terakhir, Anda akan menerima email konfirmasi yang menyatakan bahwa isi ulang saldo Anda telah berhasil atau gagal. + +![Success Failed Confirmation](../images/success_failed_confirmation.png) + +### Isi Ulang via Transfer Instan BCA + +Sekarang, kami memberi kemudahan metode isi ulang untuk para nasabah BCA. Transfer instan BCA memungkinkan Anda untuk mengisi ulang saldo secara instan tanpa perlu mengonfirmasi bukti transfer secara manual. Sistem kami akan otomatis menambahkan saldo Anda saat Anda mengisi ulang. + +Untuk melakukan isi ulang via transfer instan BCA, silakan ikuti langkah-langkah berikut: + +1. Klik tab “Transfer Instan BCA” pada menu Isi Ulang Saldo. +2. Masukkan nominal saldo yang ingin Anda isi, dengan pengisian minimal Rp10.000. + + +![BCA Unique Code Input Amount](../images/desktop_bcauniquecode_input_amount.png) + +3. OY! akan menampilkan jumlah yang harus Anda transfer, ditambah tiga digit kode unik di belakang nominal transfer. Pastikan Anda transfer sesuai dengan jumlah yang tertera. + +![BCA Unique Code Transfer](../images/desktop_bcauniquecode_transfer.png) + +4. Setelah transaksi berhasil, saldo yang tertera pada Subtotal akan masuk ke akun OY! Anda. +Catatan: Jam operasional untuk metode ini adalah setiap hari, mulai dari pukul 03.01 pagi hingga pukul 08.00 malam. Di luar jam tersebut, Anda tidak bisa menggunakan metode ini. + +![BCA Unique Code Transfer](../images/desktop_bcauniquecode_success.png) + +## Tarik Saldo ke Rekening + +Anda dapat menarik saldo OY! ke rekening Anda dengan mengikuti langkah-langkah berikut: + +1. Masuk ke dashboard OY! +2. Pergi ke menu “Laporan Transaksi” lalu pilih “Laporan Transaksi Rekening” +3. Klik “Tarik Dana” +4. Masukkan jumlah yang ingin Anda tarik + + +![Withdrawal Top Up](../images/withdrawal_topup.png) + +4. Pilih jenis penarikan Anda. Anda dapat memilih salah satu dari opsi berikut: + + - Instan dengan biaya admin -> Penarikan Anda akan diproses dan tiba di bank penerima Anda secara real-time, tetapi Anda akan dikenakan biaya admin. Jika jumlah yang akan ditarik <= Rp 50 juta dan bank penerima adalah BCA, BRI, BNI, Mandiri, Permata, DBS, dan CIMB Niaga. + + - Manual -> Penarikan Anda akan diproses dalam waktu hingga 2 hari kerja. Jika jumlah yang akan ditarik > Rp 50 juta ATAU bank penerima bukanlah BCA, BRI, BNI, Mandiri, Permata, DBS, atau CIMB Niaga, Anda harus memilih opsi ini. Anda TIDAK akan dikenakan biaya admin jika Anda memilih opsi ini. + | + +## Laporan Transaksi - Laporan Penyelesaian + +Laporan Penyelesaian adalah daftar transaksi yang menggunakan metode pembayaran dengan waktu proses non real-time atau tidak langsung. Misalnya, untuk metode pembayaran "VA BCA" butuh waktu 2 hari kerja sejak transaksi dilakukan agar uangnya masuk. Maka setiap kali pelanggan Anda melakukan transaksi dengan VA BCA, transaksi tersebut akan tercatat dalam Laporan Penyelesaian. + +Untuk mengakses laporan penyelesaian, ikuti langkah-langkah berikut: +- Masuk ke dashboard OY! +- Pilih menu Laporan Transaksi, lalu klik Laporan Penyelesaian + + +![Settlement Report](../images/settlement_report.png) + +Laporan Penyelesaian berisi informasi sebagai berikut: + +- Total nominal untuk diselesaikan hari ini: Jumlah total yang dijadwalkan untuk diselesaikan ke saldo akun Anda hari ini. + +- Total nominal penyelesaian tertunda: Jumlah total yang belum diselesaikan ke saldo akun Anda. (status penyelesaian masih belum SUKSES) + +- Berikut adalah informasi di dalam tabel Daftar Transaksi Penyelesaian: + + +| Field Name | Description | Example | +| ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------ | +| Transaction Date | The date on which your customer performs the transaction | 11 May 2021 | +| Transaction Time | The time at which your customer performs the transaction | 17:44:09 | +| Transaction ID | A unique transaction ID given by OY | d4b26687-34b9-43d3-9d08-af440bcbaca7 | +| Partner Transaction ID | A unique transaction ID that is assigned by you for a given transaction | TXID_001 | +| Product | The product associated with a given transaction. This will be filled with VIRTUAL_ACCOUNT if the transaction comes from Virtual Account Aggregator product, or PAYMENT_CHECKOUT if the transaction comes from Payment Link product | VIRTUAL_ACCOUNT | +| Payment Method | The payment method associated with a given transaction. Possible values: VA [Bank Name], CARDS, QRIS, EWALLET SHOPEEPAY | VA BCA | +| Transfer Amount | The transaction amount (before getting deducted with admin fee | +Rp 10.000 | +| Admin Fee | The admin fee associated with a given transaction | -Rp 1.000 | +| Total Amount | The transaction amount that has been deducted with admin fee | +9,000 | +| Settlement Date | The scheduled settlement date for a given transaction | 12 May 2021 | +| Settlement Time | The scheduled settlement timestamp for a given transaction | 15:00:00 | +| Settlement Status | The settlement status for a given transaction. Possible values: WAITING (if the amount is not yet settled to your account statement balance), SUCCESS (if the amount has been settled to your account statement balance). | SUCCESS | + +Jika status penyelesaian suatu transaksi masih MENUNGGU, transaksi tersebut belum muncul di tabel laporan rekening Anda, dan jumlahnya juga belum akan ditambahkan dalam saldo Anda. + +Jika status penyelesaian suatu transaksi sudah SUKSES, transaksi tersebut akan muncul di dalam tabel laporan rekening Anda, dan jumlahnya akan ditambahkan dalam saldo Anda. + +### Callback untuk Transaksi Tertunda +Jika Anda mempunyai transaksi dengan metode pembayaran yang diselesaikan H+>0, Anda akan menerima dua callback dengan rincian sebagai berikut: + +1. Callback Pertama -> Akan dikirim setelah pelanggan Anda berhasil melakukan transaksi. Misalnya, jika pelanggan Anda melakukan transaksi pada 11 Mei 2021 pukul 14.00, saat itu juga kami akan mengirimkan callback pertama kepada Anda. Dalam callback pertama, status penyelesaian akan menjadi MENUNGGU (karena belum masuk ke saldo Anda). + +2. Callback Kedua -> Akan dikirim setelah status penyelesaian berubah dari MENUNGGU menjadi SUKSES. Misalnya, jika status penyelesaian berubah menjadi SUKSES pada 12 Mei 2021 pukul 15.00, saat itu juga kami akan mengirimkan callback kedua kepada Anda. Dalam callback kedua, status penyelesaian akan menjadi SUKSES. + + +### Unduh dan Filter Laporan Penyelesaian +Tombol Ekspor: Anda dapat mengunduh laporan penyelesaian dalam format CSV, PDF, dan XLSX. + +Filter: Anda dapat mem-filter laporan penyelesaian berdasarkan rentang tanggal transaksi, produk, metode pembayaran, rentang tanggal penyelesaian, dan status penyelesaian. + +## Notifikasi +Anda dapat mengaktifkan atau menonaktifkan pengaturan notifikasi saat saldo Anda mencapai limit, notifikasi untuk produk kirim uang, dan notifikasi untuk produk terima uang jika Anda memiliki peran super admin atau admin, berikut caranya: + +1. Masuk ke dashboard melalui https://business.oyindonesia.com/ +2. Pergi ke menu Pengaturan -> Notifikasi + ![Notif Disbursement - 1 Initial](https://user-images.githubusercontent.com/79620482/126457509-ca20c24b-9277-4be4-943e-801b79806e65.png) +3. Jika Anda ingin mendapatkan pemberitahuan melalui email jika saldo Anda mencapai limit, atur di tab "Saldo Rendah". Masukkan limit jumlah saldo dan alamat email yang ingin dikirimkan notifikasi. +4. Pilih apakah Anda ingin mengaktifkan atau menonaktifkan pemberitahuan untuk Multitransfer atau API Kirim Uang. + a. Aktifkan Pemberitahuan: Pemberitahuan melalui email akan dikirimkan untuk transaksi tertunda, gagal, dan berhasil. + b. Nonaktifkan Pemberitahuan yang Berhasil: Pemberitahuan melalui email hanya akan dikirimkan untuk transaksi tertunda dan gagal. +5. Pilih apakah Anda ingin mengaktifkan atau menonaktifkan pemberitahuan untuk Link Pembayaran atau VA: + a. Aktifkan Pemberitahuan: Pemberitahuan melalui email akan dikirimkan untuk transaksi yang sukses. + b. Nonaktifkan Pemberitahuan Berhasil: Tidak akan ada pemberitahuan melalui email. +6. Kolom Email: Memungkinkan pengguna untuk menambahkan, menghapus, dan mengedit email penerima notifikasi dalam kolom ini, Anda dapat memasukkan hingga 3 alamat email +7. Klik 'Simpan Perubahan' + + +## Multi Entity Management + +Mengelola banyak akun rekening bisnis untuk perusahaan yang memiliki franchise, toko cabang, hingga anak perusahaan dapat membuat sebagian besar pengusaha dan pemilik bisnis kehabisan tenaga. Terkadang proses pembukuan menjadi lebih rumit karena banyak data transaksi dan arus kas yang harus dikelola. + +Sebagai bentuk solusi, OY! Indonesia melalui OY! Business menghadirkan fitur Multi Entity yang memudahkan para pemilik bisnis untuk memantau arus kas bisnis, memeriksa transaksi, serta menerima dan mengirim uang. + +Multi Entity dari OY! Business memungkinkan pemilik bisnis untuk memiliki lebih dari 1 akun sekunder OY! Business. Akun sekunder ini disebut sebagai Sub-entity Account yang berada di dalam 1 akun utama yang bernama Main Entity untuk dikelola dan dikendalikan. + +Dengan fitur ini, Anda akan dapat: + +* Memiliki laporan terperinci tentang transaksi yang dilakukan oleh semua Sub-entity. +* Mentransfer saldo antara akun Main-entity ke akun Sub-entity. +* Menggunakan saldo Sub-entity anak untuk mengirimkan dana. +* Menerima uang menggunakan Link Pembayaran atas nama Sub-entity. +* Tidak dikenakan biaya admin untuk mentransfer saldo antar entity. + + +### **Cara Mendaftar dan Mengaktifkan Fitur** +1. Buat Akun OY! Indonesia +2. Aktifkan akun Anda melalui link aktivasi yang dikirim melalui email. +3. Verifikasi bisnis Anda. +4. Tunggu hingga bisnis Anda terverifikasi oleh tim kami. +5. Hubungi perwakilan tim kami untuk mengaktifkan fitur Multi Entity + + + +### **Cara menggunakan Fitur Multi Entity Management** + +Untuk menggunakan Multi Entity Management, Anda perlu menghubungkan beberapa akun. Setiap akun dapat digunakan secara independen, dan ada beberapa persyaratan yang harus Anda penuhi untuk menggunakan fitur ini. Silakan hubungi tim kami untuk informasi lebih lanjut. + + +**A. Menghubungkan Antar Akun** + +1. **Jika Anda adalah Main-Entity** + +Untuk menambahkan sub-entity, lakukan langkah berikut: +1. Masuk ke dashboard dengan akun utama +2. Pergi ke menu Multi Entity lalu klik tombol “Tambah Sub-Entity” + +![Image: As a Main Entity - Add a New Sub-entity](../images/MEM_Add_Sub_Entity_1.png) + +3. Isi username sub-entity yang ingin Anda hubungkan. Anda dapat menemukan username Anda dari menu Akun → Profil Pengguna dalam bagian username. +4. Klik Tombol “Tambah”, lalu jika akun ada di sistem, akan ada konfirmasi untuk menghubungkan. + +![Image: As Main Entity - Subs - 6. Add Child - Type Username](../images/MEM_Add_Sub_Entity_2.jpg) + +5. Setelah mengonfirmasi, permintaan akan dikirimkan ke akun yang bersangkutan untuk diterima. + +2. **Jika Anda adalah Sub-Entity** + +Untuk menerima permintaan dari main entity, silakan masuk ke dashboard dengan akun sub-entity, kemudian buka menu “Laporan Transaksi” → “Laporan Transaksi Rekening”. +![Image: As Child - AccState - 2b. Waiting approval](../images/As_Child_AccState_2b_Waiting_approval.png) + +1. Klik tombol "Cek Permintaan" yang dapat ditemukan di dekat sudut kanan atas halaman. +2. Anda akan melihat permintaan yang masuk untuk dihubungkan menjadi main entity untuk akun Anda, kemudian Anda dapat memilih akun mana yang merupakan akun yang benar untuk main entity dengan mengklik “Terima” pada akun yang sesuai. + + +![Image: As Child - AccState - 4b. 2 Requests to connect.png](../images/As_Child_AccState_4b_2_Requests_to_connect.png)![Image: As Child - AccState - 4e. Confirmation to connect](../images/As_Child_AccState_4e_Confirmation_to_connect.png) + +3. Setelah berhasil menerima permintaan dari main entity untuk terhubung, Anda akan menemukan informasi main entity Anda di dekat sudut kanan atas halaman Anda, dan selamat, Anda sekarang sudah terhubung! + +![Image: As Child - AccState - 4h. Success add new head company](../images/As_Child_AccState_4h_Success_add_new_head_company.png) + +**B. TMengisi Saldo ke akun Sub-Entity** + +Setelah terhubung dengan sub-entity Anda, Anda dapat mengisi ulang saldo akun sub-entity dengan mengakses menu Multi Entity → Daftar sub-entity, lalu ikuti langkah berikut: +1. Temukan akun yang ingin Anda isi saldonya +2. Klik tombol "Isi Ulang" +3. Isi jumlah yang ingin Anda isi +4. Klik tombol "Isi Ulang Sekarang" untuk melanjutkan +5. Anda juga dapat melakukan pengisian ulang dengan mentransfer ke salah satu Virtual Account yang disediakan (lihat "Cara Mengisi Saldo melalui VA") + + +![Image: As Parent - Subs - 5a. Top Up.png](../images/As_Parent_Subs_5a_Top_Up.png) + +**C. Mengirim Uang Menggunakan Saldo Sub-Entity** + +Dengan menu Multi Entity, Anda dapat melakukan pengiriman uang menggunakan akun sub-entity atas nama mereka. Untuk melakukannya, Anda dapat mengikuti langkah-langkah berikut: + +1. Akses menu Multitransfer dan klik tombol "+ Buat Transaksi Baru" +2. Pilih saldo sub-entity Anda sebagai sumber dana untuk melakukan pengiriman uang dengan memilih "Saldo Sub-entity Saya" dan pilih akun yang sesuai. +3. Setelah memilih, Anda dapat melanjutkan untuk melakukan pengiriman uang seperti biasa. + +![Image: Choose SoF - 2. Choose SoF subsidiary.png](../images/MEM_select_subentity_disbursement.png) + +**D. CMembuat Link Pembayaran Atas Nama Sub-Entity** + +Dengan fitur ini, Anda akan dapat menerima pembayaran dari pelanggan Anda melalui Link Pembayaran yang dibuat atas nama akun sub-entity Anda. Ketika pelanggan Anda berhasil membayar, transaksi tersebut akan dicatat dalam saldo Akun Sub-entity. Sebagai main entity, Anda mempunyai akses untuk melihat saldo dan daftar transaksi Akun Sub-entity kapan saja. + +Silakan ikuti langkah-langkah berikut untuk membuat link pembayaran atas nama akun sub-entity: + +***Melalui API*** + +Silakan hit [API Link Pembayaran](https://api-docs.oyindonesia.com/#api-create-payment-link-fund-acceptance) dan isi parameter "child_balance" dengan nama pengguna akun sub-entity yang akan Anda tetapkan sebagai tujuan saldo masuk. Ketika pelanggan Anda berhasil melakukan pembayaran, transaksi tersebut akan masuk ke dalam saldo Akun Sub-entity yang ditentukan. + + +***Melalui Dashboard*** + +1. Klik Link Pembayaran -> Sekali Pakai (jika Anda ingin membuat link pembayaran sekali pakai) atau Klik Tautan Pembayaran -> Pakai Berulang (jika Anda ingin membuat link pembayaran yang dapat digunakan berulang kali.) +2. Klik "Buat Link Pembayaran" (untuk tautan pembayaran satu kali) atau klik "Buat Link Pakai Berulang" (untuk link pembayaran yang dapat digunakan berulang kali). Anda akan melihat pop-up untuk melanjutkan proses pembuatan link pembayaran. +3. Isi "Tujuan Saldo" dengan "Saldo Saya" (jika tujuan saldo transaksi adalah ke akun main entity) atau "Saldo Sub-entity" (jika tujuan saldo transaksi adalah sub-entity Anda). Jika Anda memilih "Saldo Sub-entity", Anda akan melihat dropdown untuk memilih username sub-entity. Anda hanya dapat memilih 1 akun sub-entity. + +![MAM Payment Link](../images/MEM_select_subentity_paymentlink.png) + +## Integrasi dengan Xero + +Integrasi OY! Bisnis dengan Xero bertujuan untuk memudahkan tim keuangan Anda untuk mendapatkan informasi keuangan yang mendalam. Dengan begitu, Anda dan tim jadi mengetahui kapan dan di mana Anda harus melakukan efisiensi terhadap biaya dan mendorong produktivitas. + +Setiap transaksi yang terjadi di OY! akan otomatis tercatat dalam akun Xero Anda, baik transaksi pengeluaran maupun pendapatan. + +### Bagaimana Cara Kerjanya? + +Setelah Anda berhasil menghubungkan akun Xero Anda di OY!, setiap transaksi yang terjadi di OY! akan otomatis tercatat di akun Xero Anda. Setelah koneksi berhasil, kami akan membuat rekening bank baru, yaitu rekening saldo OY!. Setiap transaksi yang terjadi di OY! akan dicatat dalam rekening bank tersebut, baik transaksi pengeluaran maupun pendapatan. Selanjutnya, Anda akan diminta untuk melakukan pemetaan chart of account Anda di Xero. Pemetaan chart of accounts Anda akan membantu kami untuk mengalokasikan transaksi Anda ke accounts yang tepat. + +### Menghubungkan Akun Xero Anda + +- Masuk ke dashboard, lalu pilih menu Integrasi +![Integration Menu](../images/xero/menu.png) + +- Anda akan melihat pilihan app Xero. Klik "Hubungkan" untuk menghubungkan akun Xero Anda ke OY!. Anda akan diarahkan ke halaman login Xero. Setelah Anda berhasil masuk ke Xero, akan muncul notifikasi. +![Product List](../images/xero/product_list.png) + +Pratinjau untuk halaman login Xero: +![Xero's Login Page](../images/xero/login_xero.png) +![Consent Page](../images/xero/consent_page.png) + +Pratinjau notifikasi OY!: +![Notification](../images/xero/connect_success_notification.png) + +- Setelah berhasil terhubung ke Xero, kami akan membuat rekening bank baru dalam akun Xero Anda, dengan nama "OY! Indonesia Balance" di mana setiap transaksi yang terjadi di OY! akan dicatat di sana. +![New Account](../images/xero/new_account.png) + +### Memetakan Chart of Account Anda + +- Setelah Anda berhasil terhubung ke Xero, Anda akan diarahkan ke halaman untuk melakukan pemetaan chart of account Anda. +![Configure Account Page](../images/xero/coa_mapping.png) + +- Kemudian, lakukan pemetaan setiap produk ke account yang sesuai. Setiap transaksi yang menggunakan setiap produk akan dimasukkan ke dalam chart of account yang Anda tentukan di halaman ini. +![Mapping Modal](../images/xero/coa_mapping_modal.png) +![Mapping Modal](../images/xero/coa_mapping_modal_2.png) + +Catatan: Khusus untuk produk Multitransfer, Anda dapat menentukan chart of account nanti dalam proses pembuatan transaksi pada halaman "Masukkan Detail" di langkah ke-3, pada kolom kategori transaksi. + +![Bulk Disbursement CoA](../images/desktop_accountingapp_bulkdisbursement.png) + +- Anda dapat mengubah pemetaanchart of account kapan saja sesuai kebutuhan, cukup pergi ke menu Integrasi > Xero > Konfigurasi. + +### Record Transaction to Xero + +- Now, your OY! account has been connected to your Xero account and each products has been mapped to its corresponding Chart of Account. +- Any success transaction that happened in OY! will be recorded to Xero with the corresponding Account you defined in Mapping Account menu. +- Transaction in OY! will be treated as SPEND or RECEIVE money transaction in a bank account (OY! Balance). +- Each transaction will contains of two rows. Row 1 contains of amount of transaction where row 2 contains of admin fee. +- In this version, any tax will be excluded. Means that you will need to input manually the tax invoice you obtained from OY! team to your Xero. +![Recorded Transaction](../images/xero/recorded_transaction.png) +![Recorded Transaction](../images/xero/recorded_transaction_2.png) + + +### Disconnected your Xero + +1. To disconnect your Xero account from OY!, open the Integration menu on the sidebar. Then select “Disconnect”. +2. Then, you will be disconnected from Xero. Any transaction that you execute via OY! will not be recorded into Xero. + +## Accurate Integration +Accurate is a local accounting platform that is mainly used by companies in Indonesia. We have integrated our system to Accurate system in order to help you optimize your workflow. By connecting your OY! account to your Accurate account, you do not have to log in to your Accurate account. This means that you do not have to manually record your transaction that happened in OY! to Accurate. You will get a seamless experience of recording your transaction. + +### How does it works? +Once you have connected your Accurate account in OY!, any transaction that happened in OY! will be automatically recorded to your Accurate account. Upon successful connection, we will trigger the creation of a new Bank Account, which is OY! Balance Bank Account. Transactions that happened in OY! will be recorded in that bank account, to PEMASUKAN or PENGELUARAN module. Then, you will be required to map your Accurate’s Chart of Account (in Accurate, it is Akun Perkiraan). Mapping your Chart of Accounts will help us to put your transaction into the correct Account. + +### Connect your Accurate +1. Login to your OY! dashboard. In the sidebar, select the Integration menu. +2. You will see Accurate menu. Click “Connect” to connect your Accurate account to OY!. You will be redirected to Accurate’s login page. + +![Connect Accurate](../images/desktop_accurate_connect.png) + +3. After login to your Accurate account, click Allow to allow conection between OY! and Accurate. +![User Consent Accurate](../images/desktop_accurate_authorize.png) + +4. Next, please select an Accurate database that you want to connect with your OY! account. +![Accurate Select Database](../images/desktop_accurate_select_database.png) + +5. Once you have successfully connected to Accurate, a notification in your OY! dashboard will appear and you can see your status is now "Connected/Terhubung". + +![Accurate Connected](../images/desktop_accurate_connected_status.png) + +6. Upon success connecting to Accurate, we will create a new Bank Account into your Accurate account, named “OY! balance” in which any transaction that happened in OY! will be recorded there. Your current OY! balance will also be mapped there. + +7. In your Accurate Dashboard, you can see that OY! Indonesia app will be listed in the Accurate Store >> Aplikasi Saya menu. +![Accurate Listing](../images/desktop_accurate_listing.png) +![Accurate Aplikasi saya](../images/desktop_accurate_aplikasisaya.png) + +8. Note: A fee of IDR 20k/month will be added to your Accurate billing. + +### Map your Chart of Account (Akun Perkiraan) + +1. Click on "Configure" to map your Chart of Account (Akun Perkiraan). +2. Then, map each product to the corresponding Account. Any transaction using each product will be put into the Chart of Accounts you defined on this page. + +![Accurate Config COA](../images/desktop_accurate_config_coa.png) + +Note: Special for Bulk Disbursement product, you can define the Chart of Account later in the campaign creation process - Step 3 Input Detail page. In the Transaction Category field. + +![Bulk Disbursement CoA](../images/desktop_accountingapp_bulkdisbursement.png) + +3. You can change the CoA mapping anytime as needed, just go to Integration >> Accurate >> Configure. + + +### Record Transaction to Accurate +1. Now, your OY! account has been connected to your Accurate account and each product has been mapped to its corresponding Chart of Account. +2. Any successful transaction that happened in OY! will be recorded to Accurate with the corresponding Account you defined in the Mapping Account menu. +3. Transactions in OY! will be recorded in Pengeluaran or Pemasukan module, under OY! Balance bank account. It will also credited/debited your OY! Balance Bank Account. +4. Note: For some reasons, we do not map Top up and Withdraw balance transactions to Jurnal. Therefore, you should adjust your bank’s Cash Bank account balance as well as OY! Indonesia Cash Bank account balance in your Accurate dashboard everytime you execute top up and withdraw transactions. + + +### Disconnected your Accurate +1. To disconnect your Accurate account from OY!, open the Integration menu on the sidebar. Then select “Disconnect”. + +![Accurate Connected](../images/desktop_accurate_connected_status.png) + +2. Then, you will be disconnected from Accurate. Any transaction that you execute via OY! will not be recorded in Accurate. +3. Note: To remove the bill, please make sure you also uninstall OY! app in your Accurate dashboard. + + +## Jurnal Integration +Jurnal is a local accounting platform that is mainly used by companies in Indonesia. We have integrated our system to the Jurnal system in order to help you optimize your workflow. By connecting your OY! Account, you do not have to manually record your transaction that happened in OY! to Jurnal. You will get a seamless experience of recording your transaction. + +### How does it work? +Once you have connected your Jurnal account in OY!, any transaction that happened in OY! will be automatically recorded to your Jurnal account. Upon successful connection, we will trigger the creation of a new Bank Account, which is OY! Balance Bank Account. You will be required to map your Jurnal’s Chart of Account. Mapping your Chart of Accounts will help us to put your transaction into the correct Account. Transactions that happened in OY! will be recorded in that bank account, into the EXPENSES or SALES INVOICE modules. + +### Connect your Jurnal +1. Before connecting your Jurnal account, you need to log in to your Jurnal account in your browser. + +![Jurnal Login](../images/jurnal_login.png) + +2. Login to your OY! dashboard. In the sidebar, select the "Integration" menu. + +3. You will see the Jurnal menu. Click “Connect” to connect your Jurnal account to OY!. + +![Jurnal Connect](../images/jurnal_integration_menu.png) + + +4. There will be a page to ask for your consent regarding this connection process. Make sure to select OY! Indonesia. Click Allow to allow connection between OY! and Jurnal. + +![Jurnal Authorization](../images/jurnal_authorization_page.png) + +5. Once you have successfully connected to Jurnal, a notification in your OY! dashboard will appear and you can see your status is now "Connected/ Terhubung". + +![Jurnal Connection Success](../images/jurnal_connection_success.png) + +![Jurnal Notification](../images/jurnal_success_notif.png) + +![Jurnal Connection Status](../images/jurnal_connection_status.png) + +6. Upon success connecting to Jurnal, we will create a new Bank Account into your Jurnal account, named “OY! Balance”. Any transaction that happened in OY! will be recorded under that bank account. Your current OY! balance will be mapped into your OY! Balance Cash Bank Account as a bank deposit transaction. + +![Jurnal OY! Balance](../images/jurnal_oy_balance.png) + +### Map your Chart of Account +1. Click on "Configure" button in the "Integrasi" menu to map your Chart of Account. + +2. Then, map each product to the corresponding Account. Any transaction using each product will be put into the Chart of Accounts you defined on this page. + +![Jurnal COA Mapping](../images/jurnal_coa_mapping.png) + +3. Note: Special for Bulk Disbursement product, you can define the Chart of Account later in the campaign creation process - Step 3 Input Detail page. In the Transaction Category field. + +![Jurnal Bulk Disburse COA](../images/jurnal_bulk_disburse_coa.png) + +4. You can change the CoA mapping anytime as needed, just go to Integration >> Jurnal >> Configure. + +5. Note: currently, we do not include mapping and recording the PPN tax. The applied tax will be recorded to Admin Fee Chart of Account. We will update this document as soon as possible once we have included mapping PPN function. + +### Record Transaction to Jurnal +1. Now, your OY! account has been connected to your Jurnal account and each product has been mapped to its corresponding Chart of Account. +2. Any successful transaction that happened in OY! will be recorded to Jurnal with the corresponding Account you defined in the Mapping Account menu. Transactions from OY! will be recorded in the Expenses or Sales Invoice module, under OY! Balance bank account and it will credited/debited your OY! balance bank account. Note: Admin Fee of Receive Money transactions will be recorded in the Expenses module. + +3. In this image, we show you a Disbursement Transaction with CoA "Iklan & Promosi" and admin fee with CoA "Komisi & Fee". It is recorded into the "Expenses" module in your Jurnal account with status LUNAS. + +![Jurnal Disburse Transaction](../images/jurnal_bulk_disburse_txn.png) + +4. Note: For some reasons, we do not map Top up and Withdraw balance transactions to Jurnal. Therefore, you should adjust your bank’s Cash Bank account balance as well as OY! Indonesia Cash Bank account balance in your Jurnal dashboard everytime you execute top up and withdraw transactions. + +### Disconnected your Jurnal +1. To disconnect your Jurnal account from OY!, open the Integration menu on the sidebar. Then select “Disconnect”. +2. Then, you will be disconnected from Jurnal. Any transaction that you execute via OY! will not be recorded in Jurnal. + + +## Authorization Callback OY! +Ketika Anda berhasil melakukan transaksi dengan produk berbasis API, OY! Akan mengirimkan callback ke sistem klien. Untuk menjaga keamanan sistem Anda, kami telah menyediakan fitur yang memungkinkan Anda untuk mengontrol dan menyetujui pengiriman callback dari OY! sebelum callback tersebut diterima oleh sistem Anda. + +Untuk saat ini, kami hanya mendukung pengaturan untuk protokol OAuth 2.0. Jika sistem Anda menggunakan protokol tersebut, Anda dapat mengatur agar callback dari OY! akan diarahkan ke proses otorisasi atau perizinan yang Anda tentukan sebelum callback tersebut diterima oleh sistem Anda. Untuk melakukan hal tersebut, Anda hanya perlu memasukkan URL otorisasi, client ID, dan client secret untuk OY! melalui dashboard Anda. + +### Cara Mengatur Authorization Callback +Anda dapat mengikuti langkah-langkah berikut untuk mengatur perizinan callback melalui dashboard: + +1. Masuk ke dashboard OY! sebagai superadmin atau admin, kemudian pilih menu Pengaturan, lalu pilih Opsi Developer. +2. Pilih tab “Authorization Callback”. Selanjutnya, Anda perlu memasukkan kredensial otorisasi Anda (URL untuk Otorisasi, client ID, dan client secret) sehingga OY! dapat melalui proses otorisasi sebelum mengirimkan callback kepada Anda. + +![Callback Authorization Configuration](../images/oy_auth_configuration.png) + +3. Klik tombol “Simpan Perubahan”. +4. Jika Anda sudah memasukkan kredensial OAuth, maka setiap callback yang dikirimkan oleh OY! akan melalui proses otorisasi atau perizinan sebelum callback dikirimkan kepada Anda. OY! akan mendapatkan token akses beserta tenggat waktu dari sistem Anda. +5. Jika Anda tidak lagi ingin callback dari OY! melewati proses otorisasi, Anda dapat menghapus semua informasi atau kredensial OAuth yang telah Anda masukkan pada halaman ini, lalu klik “Simpan Perubahan”. diff --git a/source/includes/id/_responsecodes.md b/source/includes/id/_responsecodes.md new file mode 100644 index 00000000000..23f7c8cc5e5 --- /dev/null +++ b/source/includes/id/_responsecodes.md @@ -0,0 +1,8 @@ +# Response Codes + +Possible status codes on the Payment Result Callback: + +Payment Status | Type | Meaning +-------------- | ---- | ------- +success | String | Payment by Buyer is successful and has been sent to your bank account +failed | String | Payment by Buyer is failed \ No newline at end of file diff --git a/source/includes/id/_sending-payments.md b/source/includes/id/_sending-payments.md new file mode 100644 index 00000000000..f4ebd156bd5 --- /dev/null +++ b/source/includes/id/_sending-payments.md @@ -0,0 +1,1358 @@ +# Kirim Uang + +## API Disbursement + +API disbursement product provides the capability for you to disburse to 100+ banks in Indonesia via OY! at any time. The integration process to use the API disbursement product is straight forward and the details can be checked [here](https://api-docs.oyindonesia.com/#fund-disbursement). + +### Key Features + +**Overbooking** +OY! can use the funds directly from your Mandiri or CIMB bank accounts for your disbursement needs. You will only need to top up the admin fee needed to execute the disbursements instead of the full amount of your disbursement. Please contact our [business representative](partner@oyindonesia.com) for further details about this feature. +**Check Transaction Status and Callback** + +For all disbursements executed, you will receive notifications regarding your transaction whether it is successful, failed or pending. We also provide an API for you to check the transaction status manually. IP proxy is also available upon request to enhance the security and integrity of the callback you will receive. + +**Check Balance** + +You can check your available balance at anytime to ensure that you have sufficient balance to execute a disbursement. + +### Registration and Set Up +**Prerequisites** + +* Register an account on the [OY! dashboard](https://business.oyindonesia.com/register?) + +* Activate your account through the activation link sent via email + +* Upgrade your account + +* Upgrade request is approved + +* Provide IP to be whitelisted and callback link to our business team + +* Receive an API Key from us + +* Integrate with our [API](https://api-docs.oyindonesia.com/#fund-disbursement) + +### Testing + +Once you successfully create an OY! account, you can immediately simulate disbursement via API. +Follow the below steps to test the flow: + +1. Create an account +2. Login into the dashboard +3. Change the environment to “staging” +4. Once the environment changed to staging, there will be API key staging available on the bottom left corner of the page +5. Before creating a disbursement transaction, check your available balance through API GET https://api-stg.oyindonesia.com/api/balance +6. Create a disbursement by sending a ‘POST’ request to _https://api-stg.oyindonesia.com/api/remit_ (https://api-stg.oyindonesia.com/api/remit) using your staging API key. Enter the required and optional fields, as referenced in the API reference docs (https://api-docs.oyindonesia.com/#disbursement-fund-disbursement) +7. Fill in the amount, recipient bank, recipient account, and the partner transaction-id +8. To get the status of a disbursement request, you can call the API https://api-stg.oyindonesia.com/api/remit-status, This API also offers the option for callback status under field send_callback +9. If payment is successful or failed, we will send a callback to the registered staging callback URL destination. Callback URL can be registered via our business representative. +10. The API disbursement transactions can be monitored through OY! dashboard from the “Send money - API disbursement” menu. + + +### How to Use + +In order to create disbursements, a sufficient available OY! balance is required in the account. More details and instructions about topping up to your OY! account coming soon. + +Before you execute a disbursement, you can verify the beneficiary account information from our [inquiry endpoint](https://api-docs.oyindonesia.com/#bank-account-inquiry). + +> Below is an example of the request body for inquiry: + +```shell +curl -X POST https://partner.oyindonesia.com/api/inquiry +-H 'content-type: application/json, accept: application/json, x-oy-username:myuser, x-api-key:987654' +-d '{ + "recipient_bank": "022", + "recipient_account": "7823023345" + }' +``` + +> It will return an [error message](https://api-docs.oyindonesia.com/#fund-disbursement-response-codes) if the request is not valid. Otherwise, below is the sample response parameters that will be returned: + +```json +{ + "status":{ + "code":"000", + "message":"Success" + }, + "recipient_bank":"022", + "recipient_account":"7823023345", + "recipient_name":"Budi Budianto Budiman", + "timestamp":"16-10-2021 09:55:31" +} +``` + +> + +Next, send a request body to execute a disbursement request to be sent to our [disbursement endpoint](https://api-docs.oyindonesia.com/#disbursement). + +> Below is an example of the request body for the remit: + +```shell +curl -X POST https://partner.oyindonesia.com/api/remit +-H 'content-type: application/json, accept: application/json, x-oy-username:myuser, x-api-key:7654321' +-d '{ + "recipient_bank": "022", + "recipient_account": "7823023345", + "amount":100000, "note":"Pembayaran Nov IV", + "partner_trx_id":"Tx15048563JKFJ", + "email" :"budi.s@gmail.com" + }' +``` + +> Below is the sample response parameters that will be returned: + +```json +{ + "status":{ + "code":"101", + "message":"Request is Processed" + }, + "amount":100000, + "recipient_bank":"022", + "recipient_account":"7823023345", + "trx_id":"89718ca8-4db6-40a0-a138-a9e30d82c67d", + "partner_trx_id":"Tx15048563JKFJ", + "timestamp":"16-10-2019 10:23:42" +} +``` + +> + +An enpoint to [check the transaction](https://api-docs.oyindonesia.com/#get-disbursement-status) is also available and can be accessed at anytime. + +> Below is an example of the request body: + +```shell +curl -X POST https://partner.oyindonesia.com/api/remit-status +-H 'content-type: application/json, accept: application/json, x-oy-username:myuser, x-api-key:7654321' +-d '{ + "partner_trx_id": "1234-asde", + "send_callback": "true" + }' +``` + +> The above command returns a JSON structured similar like this: + +```json +{ + "status":{ + "code":"000", + "message":"Success" + }, + "amount":125000, + "recipient_name":"John Doe", + "recipient_bank":"008", + "recipient_account":"1234567890", + "trx_id":"ABC-456", + "partner_trx_id":"1234-asde", + "timestamp":"16-10-2020 10:34:23", + "created_date": "24-01-2020 06:48:08", + "last_updated_date": "24-01-2020 06:48:39" +} +``` + +> + +A callback with the following information will be sent to the callback endpoint that you can register with us. + +> Below is an example of the request body: + +```shell +curl -X POST https://partner.oyindonesia.com/api/remit-status +-H 'content-type: application/json, accept: application/json, x-oy-username:myuser, x-api-key:7654321' +-d '{ + "partner_trx_id": "Tx15048563JKFJ" + }' +``` + +> Below is the sample response parameters that will be returned: + +```json +{ + "status":{ + "code":"000", + "message":"Success" + }, + "amount":100000, + "recipient_name":"Budi Soemitra Nasution", + "recipient_bank":"022", + "recipient_account":"7823023345", + "trx_id":"89718ca8-4db6-40a0-a138-a9e30d82c67d", + "partner_trx_id":"Tx15048563JKFJ", + "timestamp":"16-10-2019 10:40:23", + "created_date": "16-10-2019 10:23:42", + "last_updated_date": "16-10-2019 10:34:23" +} +``` + +> + +You can also [check your balance](https://api-docs.oyindonesia.com/#get-balance) anytime to ensure you have sufficient balance from our endpoint. + +> Below is an example of a request body to check the balance: + +```shell +curl -X GET 'https://partner.oyindonesia.com/api/balance' +-H 'Content-Type: application/json' +-H 'Accept: application/json' +-H 'X-OY-Username: janedoe' +-H 'X-Api-Key: 7654321' +``` + +> Below is the sample response parameters that will be returned: + +```json +{ + "status":{ + "code":"000", + "message":"Success" + }, + "balance":100000000.0000, + "pendingBalance":2000000.0000, + "availableBalance":98500000.0000, + "overbookingBalance":98500000.0000, + "timestamp":"10-12-2019 12:15:37" +} +``` + +> + +Lastly, all transactions can be monitored from the OY! dashboard which includes all the transaction details. + +![API Disbursement](../images/api_disburse_error_reason.png) + +![API Disbursement](../images/api_disburse_success.png) + + +For further details on the parameters definition and proper usage, please refer to our [API Documentation](https://api-docs.oyindonesia.com/#fund-disbursement). + + +## Bulk Disbursement + +Our Bulk disbursement product provides the capability to execute disbursements to multiple beneficiaries with a single xlsx or csv file upload ("Campaign") up to 25,000 transactions. Bulk disbursement is made through the OY! dashboard, where details regarding the disbursement campaign can be found. No technical integration is required to use this product. + +### Key Features + +**Overbooking** +OY! can use the funds directly from your Mandiri or CIMB bank accounts for your disbursement needs. You will only need to top up the admin fee needed to execute the disbursements instead of the full amount of your disbursement. Please contact our [business representative](partner@oyindonesia.com) for further details about this feature. + +**Account Management** + +When you first create an account, your account will be assigned as a Super Admin role. As a Super Admin, you have the ability to create new sub-accounts and assign different roles to your team such as Admin, Maker and Approver that are applicable for bulk disbursement. The Super Admin and Admin can also edit or delete created sub-accounts. + +*Note: it is not necessary to create new sub-accounts in order to use bulk disbursement. The Super Admin and Admin roles allows you to directly create and approve bulk disbursements.* + +Detailed step-by-step instructions on setting up user management and the different role types coming soon. + +**Multi-Layer Approval** + +Multi-layer Approval will improve your control over your bulk disburse transaction especially for big amount of money. You can setup up to 3 layers of approver before the transaction instruction is executed. By assigning proper approver and amount limitation, you can avoid a huge trouble on your business operational caused by incorrect transfer amount. + +**Overall Campaign Summary** + +Keep track of all the details of the entire campaign such as the total amount of disbursement, total number of transactions, and the maker and approver related information of a campaign. + +**Transaction Details** + +Itemized details of each individual transaction, including their respective statuses: success, pending, or failed. + +### Registration and Set Up +**Prerequisites** + +* Register an account on the [OY! dashboard](https://business.oyindonesia.com/register?) + +* Activate your account through the activation link sent via email + +* Upgrade your account + +* Upgrade request is approved + +### Testing + +1. Log on your OY! dashboard +2. Choose "Staging" environment +3. Click "Send Money" menu, and choose "Bulk disbursement" +4. Click "Create Disbursement" +5. Fill in the necessary details by following the steps explained in the “How to Use” section + +### How to Use + +In order to create disbursement campaigns, a sufficient available OY! balance is required in the account. If there is an insufficient available balance in the account, campaigns can still be created but not approved. + +*1. Create Disbursement*: On the OY! dashboard, navigate to Send Payments > Bulk Disburse on your left menu bar. Click “Create Disbursement” on the far righthand side of that page to create a new bulk disbursement campaign. + +*2. Create Campaign Details*: Fill in the campaign details with 2 options: + +![Create Bulk - 1 Initial](https://user-images.githubusercontent.com/79620482/128454459-16770aa6-486a-40b8-a73b-f2c4bb1910e3.png) + + +a. upload an xlsx or csv file + +Please upload an xlsx or csv file with each individual transaction’s details of your bulk disbursement campaign. An example template for both file types are available for download on the OY! dashboard. The following list of items are required in your CSV file. + +Column | Description | Example +------ | ----------- | ----------- +Name | Recipient Name | Budy +Email | Recipient Email (can contain up to 5 emails with a total maximum of 255 characters, incoming transaction notifications will be sent to these emails) | Budi@email.com +Amount | Amount in IDR (only numbers) | 100000 +Bank Code | [Destination Bank](https://docs.oyindonesia.com/#disbursement-bank-codes) | 014 +Bank Account Number | Recipient Bank Account Number | 12341234 +Phone Number | Recipient Phone Number | 62812341234 +Notes (Optional) | Transaction Notes | + + b. add disbursement detail manually. : +choose ‘add disbursement detail manually’ and fill out a campaign name and campaign description in the provided spaces. These details are strictly used as your tracking information only and will not be shared to the transaction recipients. + +c. [Staging only] You can replicate failed status on individual transaction within the campaign by fill in _Bank Account Number_ value with `3000000`. Another value will be processed normally. + +*3. Re-verify all the Information and Submit*: Once your xlsx or csv file is uploaded or filled out manually, you can verify all of the information uploaded from the file from the table displayed. If there is any incorrect submission such as invalid entry due to special characters, a red box will appear to highlight the entry that should be corrected. Issues must be resolved before a campaign can be submitted + +![Create Bulk - 2 File Uploaded](https://user-images.githubusercontent.com/79620482/128454606-2838f6ca-aad1-446a-becb-75d780460708.png) + + +*4. Validate Name Matching* +After all the issue has been resolved, user able to click submit and there will be popup shown to validate each recipient *name *with their *Bank Account Name* as shown below: + +![Create Bulk - 3 Ask Name Validation](https://user-images.githubusercontent.com/79620482/128454745-c5a42979-30ba-44c2-a405-bbe9b0325677.png) + +if you choose YES: if there is a name difference, a popup name validation with details of mismatched transactions will be displayed. if the information inputted is invalid, you could edit the information and choose the ‘ validate’ button to revalidate the data, or you could click the ‘ignore mismatch’ button to ignore the name matching validation and to process the disbursement. + +![Create Bulk - 5e Mismatch Names](https://user-images.githubusercontent.com/79620482/128454900-3a007579-2701-4305-9f9d-15cc8a09ab2f.png) + + +If there is no issue with the details uploaded, a validate and submit button will be available at the bottom of the list of transactions, indicating that all information is valid. Click “Submit” to complete creating the bulk disbursement campaign. + +![Create Bulk - 6a All data inquired without Name Matching](https://user-images.githubusercontent.com/79620482/128454982-b1ab6dfc-c279-4baf-a0e6-a2983a069488.png) + + +*5. Approve/Cancel Campaign**: Once the bulk disbursement campaign is created, a new status of `waiting approval` will appear. Approve the campaign by clicking the “Approve” button. If you want to cancel a campaign, click the “...” button and select “Cancel”. + +![Bulk Disburse](../images/bulk_disburse_4.png) + +Once the bulk disbursement campaign is approved, details regarding the campaign can no long er be changed. This includes changes made to individual transactions and their respective recipient information. + +The balances will also immediately reflect changes. For more information about the different types of balances, click here. You will also receive an email with the campaign information summary (“Outgoing Transfer Alert”) when transactions are executed. + +*Note:* +- *Depend on the approval layer that you configured, this transaction should be approved by all layer before it can be executed by the system.* +![Bulk Disburse](../images/multi-layer-approval.png) + +- *Multiple campaigns can be approved at a time as long as there is sufficient available balance to complete campaigns that have already been approved but are still in queue to be processed.* + +*6. Keep Track of Campaign Details**: To check the details of the bulk disbursement campaign, click on the campaign name to find the campaign summary and its recipient list. Keep track of the both the overall campaign status and the status of individual transactions through the page. + +![Bulk Disburse](../images/bulk_disburse_5.png) + +*7. Status: In-Progress, Finish, and Cancel**: Congratulations! You just made your first bulk disbursement with OY! Below are a list of statuses you will find on the OY! dashboard. + +_In-Progress_ + +As your individual disbursements are executed, the status of your bulk disbursement campaign will indicate an in-progress status. + +![Bulk Disburse](../images/bulk_disburse_error_reason.png) + +_Incomplete_ + +The status of your bulk disbursement will change to incomplete once all of the listed transactions have been executed and the relevant final statuses of Failed or partially Failed have been assigned. The failed transactions will be shown the failed reason and can be retried. +![Notif Pending - 1 Initial](../images/bulk_disburse_list_incomplete.png) + + +_Finish_ + +Once all of the listed transactions have been completed and the final status of success for all transactions has been achieved, the status of your bulk disbursement campaign will change to Finish. The recipients should have all received an email detailing an “Incoming Transfer Alert.” You can also download a report of the campaign details directly through the OY! dashboard. +![Bulk Disburse](../images/bulk_disburse_finish.png) + +_Cancel_ + +If you choose not to approve your disbursement campaign, the status of your bulk disbursement campaign will indicate a cancelled status. + +![Bulk Disburse](../images/bulk_disburse_cancel.png) + +You can also double check each of your transactions by navigating to the account statement page on the OY! dashboard. + + +## Claim Fund +Claim Fund product enable you to do disbursement without knowing your recipient bank account at first. You will simply create a link for them to fill-out bank account information and the payment will be processed by our system. +This feature will remove you from the hassle of collecting your customer information manually then doing multiple bank transfer. +Best use of this feature is : refunds, reimbursement claim, any disburse transaction in which the destination is not your regular partner. + +At the moment, Claim Fund product is available only on OY! Business Dashboard. + +### Transaction Flow + +![Transacation Flow](../images/claim-fund-flow.png) + +### Use Case +1. Refund for purchase transaction +2. Any money transfer transaction where you don't have recipient bank information + +### Registration and Set Up + +**Prerequisites** + +* Register an account on the OY! dashboard (https://business.oyindonesia.com/register?) +* Activate your account through the activation link sent via email +* Upgrade your account +* Upgrade request is approved + + +### Testing +1. Log on your OY! dashboard +2. Choose "Staging" environment +3. Click "Send Money" menu, and choose "Claim Fund" +4. Click "Create Claim Fund" +5. Fill in the necessary details by following the steps explained in the “How to Use” section + +### How to Use +In order to execute claim fund transaction successfully, a sufficient available OY! balance is required in the account. However, if there is an insufficient available balance, claim fund transaction can still be created but the approval will failed. + +**1. Business Dashboard - Create Claim Fund** + +* Create Claim Fund: On the OY! dashboard, navigate to Send Money > Claim Fund on your left menu bar. Click `Create Claim Fund` button on the far righthand side of that page to create a new claim fund transaction. +![Claim Fund Landing Page](../images/claim-fund-landing.png) +![Create Claim Fund - input data](../images/claim-fund-create.png) + +* Please fill-out the information accordingly. Below table is the description of each fields: + +| Column | Description | Example | +|------------------------------------ |------------------------------------------------------------------------------------------------------------------------------------------------------------------- |-------------------- | +| Amount to Claim | Amount of money to be sent | 1000000 | +| Expiration Duration | How long does this claim link be active. After expiration time, customer will not be able to submit their information then new claim fund link has to be created. | 12 Hours | +| Set as default expiration duration | Select this option to make it default expiration time for the next claim fund transaction. | - | +| Partner Transaction ID | Unique identifier for the recipient. | CF00001 | +| Note | additional remarks for recipient | Refund transaction | +| Recipient Name | Recipient Name | Dwiki Dermawan | +| Email | Recipient Email | dwiki@gmail.com | + +* Click `Create Claim Fund` button to submit the transaction. Your recipient will get notified of this claim fund transaction through email. Transaction link will be attached on this email. + +* Successful claim fund transaction will be listed on the claim fund transaction listing with INITIATED status. +![Create Claim Fund - Success](../images/claim-fund-create-success.png) + +* Please be noted that this transaction still need account detail to be filled-out by the recipient. + +**2. Fund Recipient - Input Account Information** + +* On the notification email, user click the `Ajukan Klaim Dana` link to get into claim fund input page. +![Create Claim Fund - Email](../images/claim-fund-user-email.png) + +* User should fill-out the detail information so that OY! system can continue with the approval process. +![Create Claim Fund - input detail](../images/claim-fund-input-detail.png) +![Create Claim Fund - input submitted](../images/claim-fund-input-submitted.png) + +**3. Business Dashboard - Approve Transaction** + +Transaction need to go through approval process to ensure that the money will be delivered to correct recipient and sufficient amount is available. + +* Approve claim fund transaction: On the OY! dashboard, navigate to Send Money > Claim Fund on your left menu bar. Transactions that already have user detail will be marked with `WAITING APPROVAL` status. +![Create Claim Fund - partner approval](../images/claim-fund-partner-approval1.png) + +* You can approve the transaction directly from this screen by clicking Approve button, or go to detail transaction to see more information before approve. +![Create Claim Fund - partner approval](../images/claim-fund-partner-approval2.png) + +* Click approve button to release the transaction to user. + +* The transaction is now marked as `IN PROGRESS` + +* Your recipient should get the money delivered to their account immediately. + +* In parallel, your customer will also get email notification about successful claim fund transaction. +![Create Claim Fund - user success](../images/claim-fund-user-success.png) + +## API Biller + +API biller product provides the capability for you to pay the bill products. With 130+ types of billing products, you can provide numerous bill payment options with ease and in real-time. +The integration process to use the API biller product is straight forward and the details can be checked [here](https://api-docs.oyindonesia.com/#biller-api). + +### Transaction Flow + +![Transacation Flow](../images/Flow_API_Biller.png) + +### Key Features + +**Overbooking** +OY! can use the funds directly from your Mandiri or CIMB bank accounts for your bill payment needs. Please contact our [business representative](partner@oyindonesia.com) for further details about this feature. + +**Check Transaction Status and Callback** + +For all bill inquiry & bill payment executed, you will receive notifications regarding your transaction whether it is successful, failed or pending. We also provide an API for you to check the transaction status manually. IP proxy is also available upon request to enhance the security and integrity of the callback you will receive. + +**Check Balance** + +You can check your available balance at anytime to ensure that you have sufficient balance to execute a bill payment. + +### Registration and Set Up +**Prerequisites** + +* Register an account on the [OY! dashboard](https://business.oyindonesia.com/register?) + +* Activate your account through the activation link sent via email + +* Upgrade your account + +* Upgrade request is approved + +* Provide IP to be whitelisted and callback link to our business team + +* Receive an API Key from us + +* Integrate with our [API](https://api-docs.oyindonesia.com/#biller-api) + +### Testing + +Once you successfully create an OY! account, you can immediately simulate bill payment via API. +Follow the below steps to test the flow: + +1. Create an account +2. Login into the dashboard +3. Change the environment to “demo” +4. Once the environment changed to demo, there will be API key demo available on the bottom left corner of the page +5. Before creating a bill payment transaction, check your available balance through API GET _https://api-stg.oyindonesia.com/api/balance_ +6. Request inquiry for the bill you want to pay by sending a ‘POST’ request to _https://api-stg.oyindonesia.com/api/v2/bill_ using your staging API key. Enter the required and optional fields, as referenced in the API reference docs (https://api-docs.oyindonesia.com/#bill-inquiry-biller-api) +7. Fill in the customer-id, product-id, and the partner transaction-id. You will get the detail information about the bill that you want to pay. +8. After successful inquiry, you should do the payment process by sending a ‘POST’ request to _https://api-stg.oyindonesia.com/api/v2/bill/payment_. Enter the required and optional fields, as referenced in the API reference docs (https://api-docs.oyindonesia.com/#pay-bill-biller-api) +8. To get the status of a bill payment request, you can call the API https://api-stg.oyindonesia.com/api/v2/bill/status +9. If payment is successful or failed, we will send a callback to the registered staging callback URL destination. Callback URL can be registered via our business representative. +10. The API biller transactions can be monitored through OY! dashboard from the “Send money - API biller" menu. + + +### How to Use + +In order to create API biller transaction, a sufficient available OY! balance is required in the account. More details and instructions about topping up to your OY! account can you see here https://docs.oyindonesia.com/#top-up-oy-dashboard-tutorial. + +Before you execute the bill payment, you have to verify the bill information from our [bill inquiry endpoint](https://api-docs.oyindonesia.com/#bill-inquiry-biller-api). + +> Below is an example of the request body for inquiry: + +```shell +curl -X POST https://partner.oyindonesia.com/api/v2/bill +-H 'content-type: application/json, accept: application/json, x-oy-username:myuser, x-api-key:987654' +-d '{ + "customer_id": "512233308943", + "product_id": "plnpost", + "partner_tx_id": "Tx15048563JKFJ" + }' +``` + +> It will return an [error message](https://api-docs.oyindonesia.com/#api-biller-response-codes-biller-api) if the request is not valid. Otherwise, below is the sample response parameters that will be returned: + +```json +{ + "status":{ + "code":"000", + "message":"Success" + }, + "data": { + "tx_id": "a3d87877-e579-4378-844b-c06294fc9564", + "partner_tx_id": "Tx15048563JKFJ", + "product_id": "plnpost", + "customer_id": "512233308943", + "customer_name": "Plg.,De'mo 512233308943", + "amount": 282380, + "additional_data": "{\"customer_id\":\"512233308943\",\"customer_name\":\"Plg.,De'mo 512233308943\",\"admin_fee\":\"2.500\"}" + } +} +``` + +> + +Next, send a request body to execute a bill payment request to be sent to our [bill payment endpoint](https://api-docs.oyindonesia.com/#pay-bill-biller-api). + +> Below is an example of the request body for the bill payment: + +```shell +curl -X POST https://partner.oyindonesia.com/api/v2/bill/payment +-H 'content-type: application/json, accept: application/json, x-oy-username:myuser, x-api-key:7654321' +-d '{ + "partner_trx_id":"Tx15048563JKFJ", + "note" :"biller transaction test" + }' +``` + +> Below is the sample response parameters that will be returned: + +```json +{ + "status":{ + "code": "102", + "message": "Request is In progress" + }, + "data": { + "tx_id": "a3d87877-e579-4378-844b-c06294fc9564", + "partner_tx_id": "Tx15048563JKFJ", + "product_id": "plnpost", + "customer_id": "512233308943", + "customer_name": "Plg.,De'mo 512233308943", + "amount": 282380, + "note": "biller transaction test" + }, +} +``` + +> + +An endpoint to [check the transaction](https://api-docs.oyindonesia.com/#get-bill-payment-status-biller-api) is also available and can be accessed at anytime. + +> Below is an example of the request body: + +```shell +curl -X POST https://partner.oyindonesia.com/api/b2/bill/status +-H 'content-type: application/json, accept: application/json, x-oy-username:myuser, x-api-key:7654321' +-d '{ + "partner_trx_id": "Tx15048563JKFJ" + }' +``` + +> The above command returns a JSON structured similar like this: + +```json +{ + "status":{ + "code": "000", + "message": "Success" + }, + "data": { + "tx_id": "a3d87877-e579-4378-844b-c06294fc9564", + "partner_tx_id": "Tx15048563JKFJ", + "product_id": "plnpost", + "customer_id": "512233308943", + "customer_name": "Plg.,De'mo 512233308943", + "amount": 282380, + "additional_data": "\"{\\\"bill_period\\\":\\\"FEB2022\\\",\\\"total_amount\\\":\\\"282.380\\\",\\\"customer_id\\\":\\\"512233308943\\\",\\\"customer_name\\\":\\\"Plg.,De'mo 512233308943\\\",\\\"admin_fee\\\":\\\"2.500\\\",\\\"settlement_date\\\":\\\"09/03/2022 16:49\\\"}\"", + "status": "SUCCESS" + }, +} +``` + +> + +A callback with the following information will be sent to the callback endpoint that you can register with us. + +You can also [check your balance](https://api-docs.oyindonesia.com/#get-balance) anytime to ensure you have sufficient balance from our endpoint. + +> Below is an example of a request body to check the balance: + +```shell +curl -X GET 'https://partner.oyindonesia.com/api/balance' +-H 'Content-Type: application/json' +-H 'Accept: application/json' +-H 'X-OY-Username: janedoe' +-H 'X-Api-Key: 7654321' +``` + +> Below is the sample response parameters that will be returned: + +```json +{ + "status":{ + "code":"000", + "message":"Success" + }, + "balance":100000000.0000, + "pendingBalance":2000000.0000, + "availableBalance":98500000.0000, + "overbookingBalance":98500000.0000, + "timestamp":"10-12-2019 12:15:37" +} +``` + +> + +Lastly, all transactions can be monitored from the OY! dashboard which includes all the transaction details. + +![API Biller](../images/API_Biller.png) + + +For further details on the parameters definition and proper usage, please refer to our [API Documentation](https://api-docs.oyindonesia.com/#biller-api). + +## Feature: Resend Callback + +### Key Features + +Retry Callback allows you to resend a callback of your transaction to your system. Initially, OY! will send a callback to your system after your transaction status has been updated. If your system failed to receive the callback, this feature can help you to retry the callback process. The process can be done in two ways + + +1. Automated retry callback +If the callback is not successfully received on the first try, the system will automatically retry the callback delivery. If that callback is still not received by the client's system, the system will automatically retry until 5 occurrences. The interval of the sending process will be detailed in the Callback Interval section. If all automated Retry Callbacks have been sent but still returned failed, the system will send an email notification to the email address set in the configuration. + +2. Manual retry callback +Besides the automated process, you can manually request a callback via the dashboard. + +### Registration and Set Up + +Follow the instruction below to activate retry callback + +1. Login to your account in OY! Dashboard +2. Open “Settings” and choose “Developer Option”. Choose “Callback Configuration” +3. Fill your callback URL in the related product that you want to activate. Make sure the format is right. You can click URL String Validation button to validate the URL format. +4. If you want to activate automated retry callback, check the Enable Automatic Retry Callback and fill in the email. The email will be used to receive a notification if all the automatic callback attempts have been made but still fail +5. Click "Save Changes". The configuration will not able to be saved if the callback URL or/and email format are not valid. + + +![Resend Callback](../images/api_disburse_retry_callback_developer_option.png) + +Don't forget to whitelist these IPs in order to be able to receive callback from OY: 54.151.191.85 and 54.179.86.72 + +If you want to manually resend a callback, you can follow the instruction below + +1. Login to your account in OY! Dashboard +2. Open the API Disbursement menu +3. Click the "Resend Callback" button in the related transaction + + +![Resend Callback](../images/api_disburse_resend_callback.png) + + + +### Callback Interval +1st retry: realtime (after the first failed log received) +2nd retry: 1 min (after callback failed log for the 1st retry is received) +3rd retry: 2 mins (after callback failed log for the 2nd retry is received) +4th retry: 13 mins (after callback failed log for the 3rd retry is received) +5th retry: 47 mins (after callback failed log for the 4th retry is received) + +If all automated Retry Callback (all the 5 attempts) has been sent but we still get a Failed response from your end, our system will send an automated email notification to the email address that has been set in the configuration earlier + + +### Idempotency Key +To implement automated retry callback, you need to handle the idempotency logic in your system using the below key: + +API Disburse: trx_id + +## Account Payable + +OY! Account Payable product provides the capability to record, create approval levels, and scheduled payment for invoice payables without hassle. Account Payable is made through the OY! dashboard, so no technical integration is required to use this product. + +### How to Use Account Payable via Dashboard + +You can create new invoice to be paid and set up payment by following this step: + +1. Log on to your OY! dashboard +2. Choose "Production" environment +3. Click "Pay Invoice" under Account Payable menu +4. Click "Invoice List" +5. Choose "Create New Invoice" +6. Upload your invoice document to help you easier record the invoice by click "Browse to Upload" or Drag & drop to the invoice area +7. Fill in the necessary details + + Parameter | Description + ------ | ----------- + Purchase Type | You can choose between purchase order, service fee, bill, subscription fee, and reimbursement + Invoice Number | The number of the invoice that you get from your vendor/supplier + Invoice Date | The date of the invoice + Due Date | Due date of a transaction as mentioned in the invoice. Your approver will be reminded to approve on D-7, D-3, and D-1 from the invoice due date + PO/PR Number (optional) | The reference PO/PR number from your company to track this invoice + Note | The note for this invoice + Vendor | The name of the vendor that this invoice belongs to. You can choose the name of the vendor from the dropdown menu. To create a new vendor, follow the instruction here + Product Description | The name and/or description of the product + Quantity | The quantity of the product + Price | Unit price of the product + Total | Total price of the product (Total = Quantity x Price) + Subtotal | The total price of all the products + PPn | PPn that should be paid to the vendor. PPn is calculated from subtotal amount. You can set up the tax during vendor addition or edit in 'Vendor Management' menu under Account Payable + PPh | PPh that should be deducted from the vendor. PPh is calculated from subtotal amount.You can set up the tax during vendor addition or edit in 'Vendor Management' menu under Account Payable + Total Pay to Vendor | Total amount that will be paid to vendor on scheduled date, post approval + Reference Documents (Upload document) | The supporting documents that you want to record related to this invoice. Accept PDF files only. Maximum 7 documents (maximum 2.5MB each) + Note: Maximum 20 rows for line item detail + + **Image Account Payable** + ![AP Invoice Creation One](../images/accountPayable/invoice_creation_1.png) + +8. Continue to set up 'Invoice Payment Details'. You can set up the payment to one time payment by choosing 'Full Payment' or multiple times payment by choosing 'Partial Payment'. + + Parameter | Description + ------ | ----------- + Payment Amount | Amount that will be automatically paid to vendor on scheduled date + Due Date | The due date of the payment. The due date cannot do back date or more than due date that set in the first page (record invoice). Notification will be send to approval D-7, D-3, and D-1 if the status is waiting aproval + Status | Status of the invoice payment. You can choose 'Paid' for record intention and this amount will not be paid by system. Choose 'Unpaid' for incoming transaction that needs to be paid + Scheduled Payment | Time of the payment. You can prepare the balance prior to scheduled payment time and make sure the payment date is based on your preference and will only be executed post approval + Remaining Amount | Total pay to vendor - subtotal. This amount should be 0 to continue the process + + **Image Payment Page** + ![AP Invoice Creation Two](../images/accountPayable/AP_scheduled_payment.png) + + ![AP Invoice Scheduled Payment Detai](../images/accountPayable/AP_Scheduled_Payment_Details.png) + + ![AP Detail Transaction](../images/accountPayable/AP_Trx_detail.png) + +9. Status: Waiting Payment, Partially Paid, Complete and Cancelled +Congratulations! You have finished your first invoice payable set up. Below are the list of statuses you will find on 'Invoice List' + + Parameter | Description + ------ | ----------- + Partially Paid | Multi times payment or partially paid that not finish yet. You can click invoice number to find the partial payment details in 'payment transaction' tab + Waiting Payment | Waiting for approval or balance not enough + Cancelled | Invoice has been cancelled + Complete | All payment of the invoice is complete + + **List of the Invoice Payable** +![AP Invoice List](../images/accountPayable/AP_Trx_detail.png) + + + +### How to Create, Edit and Inactivate Vendor Data ### + +**Add New Vendor for Account Payable** + +1. Click 'Add' in the 'Vendor' field in the 'Create Payable Invoice' page. +2. Fill in the necessary details +3. Click 'Add Vendor' after complete registration of new vendor + +Parameter | Description +------ | ----------- +Vendor ID (Optional) | Unique ID of the vendor from your company. This is not mandatory +Vendor Name | The company/vendor name. Make sure the vendor name matches the vendor NPWP (if any) to help your company tax record +Vendor Address (Optional) | Vendor address to be recorded. This is not mandatory +Bank Name | Recipient bank name. You can choose using drop down +Account Number | Recipient bank account number. You can check the inquiry by clicking 'Get Account Name' after filling the account number +PIC Name | The PIC name of this vendor +PIC Email | The PIC or recipient email. Payment/transfer receipt will be send automatically to this email after complete payment +PIC WhatsApp (optional) | The PIC WhatsApp number for your record +PPh (optional) | PPh type from this vendor. Default of the setting is Not Subject to PPh. +Vendor NPWP (optional) | The vendor NPWP number record that can be use for company reference to generate 'Faktur Pajak' +NPWP Document (optional) | Vendor NPWP document to be record. Accept PDF and JPG format. Maximum 10 MB +PPn (optional) | PPn type of this vendor. Default of the setting is Not Subject to PPn. +SKB Document (optional) | Vendor SKB Document to be record. Accept PDF and JPG format. Maximum 10 MB +Not subject of PPh | Tax will not be added upon the subtotal | +Not subject of PPn | Tax will not be added upon the subtotal | +PPN Exclusive | PPN of the subtotal will be added upon the subtotal of the invoice. For example is subtotal is 10,000, then the PPN is 11% of the 10,000 = 1,100 | +PPN Inclusive | Tax will not be added upon the subtotal because the subtotal is assumed to be tax inclusive | + +**Each vendor only have 1 type of PPh setting and 1 type of PPn setting** + +There will be an PPh email sent on the 1st day of each month that contains all the PPh from your vendors in the previous month. This report can help company with tax payment & reporting, and 'Faktur Pajak' generation to your vendor. + +**Create New Vendor** +![VM Vendor Creation](../images/vendorManagement/creation.png) + +**List Of Vendor** +![VM Vendor List](../images/vendorManagement/list.png) + +**Vendor Detail** +![VM Vendor Detail](../images/vendorManagement/detail.png) + +**How to Set Up Invoice Payable Approval** + +You can set up multi level approval from OY's users. There will be 4 type of users: Super Admin, Admin, Approver, and Maker + +**Approval Layer Set Up** + +Approval Layer can be set up using 'User Management' under 'General' menu. When you first create an account, your account will be assigned as a Super Admin role. As a Super Admin, you have the ability to create new sub-accounts and assign different roles to your team such as Admin, Maker and Approver that are applicable for bulk disbursement. The Super Admin and Admin can also edit or delete created sub-accounts. +Note: it is not necessary to create new sub-accounts in order to use Account Payable. The Super Admin and Admin roles allows you to directly create and approve Account Payable and also Bulk Disbursement. +Detailed step-by-step instructions on setting up user management and the different role types coming soon. + +**Multi-Layer Approval** + +Multi-layer Approval will improve your control over your bulk disburse transaction especially for big amount of money. You can setup up to 3 layers of approver before the transaction instruction is executed. By assigning proper approver and amount limitation, you can avoid a huge trouble on your business operational caused by incorrect transfer amount. + +Default approval: Super Admin, Admin, and Approval. + + +## Corporate Card +OY! Corporate Card product provides the capability to create customized cards that can be used to manage online subscriptions without hassle. Corporate Card can be generated through the OY! dashboard, therefore no technical integration is required to use this product. + + +### Key Features + +Condition | Description +------ | ----------- +**Card creation** | OY! can use the funds directly from your OY! balance for corporate card needs. It is essential to top-up your OY! balance based on your card limit. Please contact our business representative (https://docs.oyindonesia.com/partner@oyindonesia.com) for further details about this feature. +**Card control** | Create and control the card based on your requirements. You can set the limit amount (in Rupiah), validity period, card renewal frequency and even transaction limitations directly through OY dashboard. Moreover, you can block and deactivate corporate card real-time! Everything on your fingertips. +**Real-time transaction** | Transactions can be tracked easily through dashboard real-time. There is no need to wait until the end of month for full transaction statement + + +### How to Create Corporate Card +You can create new virtual card by following these steps: + +1. Log in to your OY! dashboard +2. Choose “Production” environment +3. Click “Corporate Card” under Expense Management menu +4. Click “Add New Card” +5. Choose “Card Type” for “Virtual” and usage frequency either single usage or multiple usage and click “Next” +6. Fill in Cardholder details and Card details +7. Once submitted, Corporate Card will be in “waiting for approval” state +8. After the approval step, the card is ready to be used for transactions. + +**Notes:** OY! balance would be put on pending once card is created. + +* Corporate Card Dashboard + +![Corporate Card Dashboard](../images/virtualCard/blankpagecard.jpg) + +* Virtual Card Type + +![Card Form](../images/virtualCard/virtualtype.jpg) + +![Card Form](../images/virtualCard/frequency.jpg) + +* Virtual Card Form + +![Card Form](source/images/virtualCard/cardholderdetails.jpg) + +![Card Form](../images/virtualCard/carddetails.jpg) + + +### How to Transact with Card +Steps to use card for online transaction: + +1. Access your card information (including remaining balance & transaction) via email and enter OTP sent to the phone number registered. +2. Once accessed, input all of you card information into merchant side under “Credit / Debit Card” Option +3. Input 16 digit number, expiry date (MM/YY) and CVV +4. Submit the information and proceed with the transaction and the transaction is successful. +5. For record purposes, you can upload the invoice for each transaction inside OY! dashboard. + +* Virtual Card Information + +![VCC Detail](../images/virtualCard/card_detail.png) + +* Virtual Card + +![VCC Transaction Detail](../images/virtualCard/detailtrxn.jpg) + +![VCC Transaction Email](../images/virtualCard/vcc_txnemail.png) + + + +**Card Status** + +Status| Description +------ | ----------- +Pending Approval | Card has been requested but approval is not yet given. Request can only stay valid for 14 days. +Active | Card is ready to be used for transaction. +Active with Warning | Card is active with balance, but only <15% balance remaining. +Inactive | Card has been deactivated, but still contain limit. +Need top-up | New card has been created, but with 0 limit OR card limit is back to 0 and pass renewal time due to insufficient balance. +Expired | Card is expired or intentionally archived permanently. +Rejected | Card is rejected by Approver. + + +**Transaction Status** + +Transaction Status | Description +------ | ----------- +Successful | Card has been used for transaction successfully +Failed | Transction was declined +Reversal | Temporary hold on card is returned to card prior settlement + + +### How to Set Approver + +1. During first time product activation, prompt to fill in Approver data will be triggered +2. Fill in the approver details +3. Tick T&C and confirm your Approver details +4. Approver will receive confirmation email + +* Add New Approver + +![VCC Add Approver](../images/virtualCard/vcc_addapprover.png) + +* Approver Form + +![VCC Approver Form](../images/virtualCard/vcc_approverform.png) + +**Notes**: Approver data cannot be added or edited through dashboard for security purposes. Please contact our business representative for helps + +Parameter | Description +------ | ----------- +Name | Approver Name +Position | Approver Role +Phone Number | Approver Phone Number +Email | Approver email for card approval purposes + + +### How to Manage Card +1. Click “See All Cards” +2. Click the card that needs to be managed + +![VCC Card List](../images/virtualCard/vcc_cardlist.png) + +**Card actions** + +Card Actions | Description +------ | ----------- +Resend Card Info | To resend card info to cardholders, in case of missing email +Edit Information | To edit the card limit. Editing card limits will lead to card temporary blockage and require reapproval flow again. +Block | To temporarily lock the card, limit remains in the card +Archive | To permanently lock the card, limit will be 0 and returned to OY! balance +Renew Limit | To renew card limit with a desired amount using OY! balance +Resend Approval Notification | To remind Approver to approve the card request in case of closet email +Delete | Only applicable for "Waiting for Approval" card + + + +### How to Set Up Card Config +1. Click “Corporate Card Configuration” +2. Select Department / Category / Approver +3. You can choose to whether add new, edit existing or delete +4. Click "Save Changes" + +* Department page prior to “Edit Department” button + +![Card Department](../images/virtualCard/departmen.jpg) + +* Category page + +![Card Category](../images/virtualCard/category.jpg) + +* Approver Page + +![Card Approver](../images/virtualCard/approver.jpg) + + +**Failed Transaction Possible Reasons** + +1. Card utilized more than + 1. requested frequency (multiple use vs single use) + 2. available limit +2. Card is inactive +3. Card is expired +4. Invalid card number +5. Invalid expiry date +6. Invalid CVV +7. Issuer network not supported + + +**Notes** + +* Transaction will be settled following bank’s instruction + +* Usage of card will directly reduce card limit and hold OY! balance + +* For refund, please kindly contact the merchant where you make the purchase at. OY! is not responsible to perform refund prior receiving the fund back from the merchant + + * Refund duration will dependent on the merchant and the bank + + * Once refund has been issued, the balance will be returned back to your OY! balance + +* It is the user’s responsibility to block card usage whenever fraudulent transactions found. OY! is not responsible for the transaction. + + +## International Transfer +International Transfer product provides the capability for users to transfer across countries from Indonesia at any time. You may create a transaction within OY! dashboard without the need for any technical integration. + +### Key Features + +**Account Management** +When you first create an account, your account will be assigned as a Super Admin role. As a Super Admin, you have the ability to create new sub-accounts and assign different roles to your team such as Admin that is applicable for international transfers. Both Super Admin and Admin may edit or delete created sub-accounts. + +Note: it is not necessary to create new sub-accounts in order to use international transfer. The Super Admin and Admin roles allows you to directly create and approve transactions. + +Detailed step-by-step instructions on setting up user management and the different role types coming soon. + +**Roles & Accessibility** +Only Super Admin and Admin roles are available to create transactions. All other roles are only allowed to view transaction list, transaction details, filter, export and edit custom column in dashboard. + +**Available destinations** +Current list of countries supported: Singapore +Current list of currencies supported: Singapore Dollars (SGD), US Dollars (USD) + +More countries such as Australia, China, Hongkong, Malaysia, South Korea and United States will be available soon. + +**Sender / Recipient Contacts** +Suppose that you want to create an international transfer on behalf of another entity, you may create an individual / business sender or recipient and input all required information about the sender or recipient. All sender and recipient information will be saved in contacts and may be reused in the future. + +**Additional Documents** +We provide a placeholder for you to upload invoice and other supporting documents for the purpose of transfer and source of funds. + +**Transaction Details** +Once a transaction is successfully created, all transaction details and updates will be recorded in OY! dashboard + +### Registration and Set Up +**Prerequisites** + +* Register an account on the [OY! dashboard](https://business.oyindonesia.com/register?) + +* Activate your account through the activation link sent via email + +* Upgrade your account + +* Upgrade request is approved + +### Testing +1. Log in to your OY! dashboard +1. Choose "Staging" environment +1. Click "Send Money" menu, and choose "International Transfer" +1. Click "Create New Transaction" +1. Fill in the necessary details by following the steps explained in the “How to Use” section +1. Note: To reproduce a failed transaction in staging environment, you may fill the recipient account number as **1234567891**. All other account numbers will result in a successful transaction. + +### How to Use +In order to create international transfers, you need to have sufficient available OY! balance is required in the account. If there is an insufficient available balance in the account, international transfers cannot be created + 1. *Create new transaction*: On the OY! dashboard, navigate to Send Payments > International Transfer on your left menu bar. Click “Create New Transaction” on the far righthand side of that page to create a new transfer. + +![Create New Transaction](../images/internationalTransfer/create_inter_remit.jpg) + + 2. *Input transfer amount details*: You may fill out the amount of transfer in two ways: + 1. Fill in the send amount (in Rupiah) you would like to transfer, along with the destination currency and country. Our system will automatically convert according to the foreign exchange rate at that time. + 1. Fill in the recipient amount (in SGD/USD) you would like to transfer, along with the destination currency and country. Our system will automatically convert according to the foreign exchange rate at that time. + +![Input Send Amount](../images/internationalTransfer/input_amount.jpg) + +*Note: If the nominal amount greater than the available balance, then our system will restrict users from proceeding* +![Balance Less Than Send Amount](../images/internationalTransfer/balance_less_than_send_amount.jpg) + + 3. *Input sender details*: Decide whether sender is an individual or business entity, and you will see the corresponding details to fill out for each. Previously saved sender contacts will be displayed at the bottom of this page. +![List Of Existing Senders](../images/internationalTransfer/list_of_existing_senders.jpg) + +Create a new individual sender by filling out this form + +![Input Individual Sender](../images/internationalTransfer/input_individual_sender.jpg) + +Create a new business sender by filling out this form + +![Input Business Sender](../images/internationalTransfer/input_business_sender.jpg) + + 4. *Input recipient details*: Decide whether recipient is an individual or business entity, and you will see the corresponding details to fill out for each. Previously saved recipient contacts will be displayed at the bottom of this page. + +![List Of Existing Recipients](../images/internationalTransfer/list_of_existing_recipients.jpg) + +This will be the form you will need to fill out for individual recipient + +Create a new individual recipient by filling out this form + +![Input Individual Recipient](../images/internationalTransfer/input_individual_recipient.jpg) + +Create a new business contact by filling out this form + +![Input Business Recipient](../images/internationalTransfer/input_business_recipient.jpg) + + 5. *Add supporting information*: In this step, we need to record source of funds, purpose of transfer for the transaction. You may also attach supporting documents to aid the compliance requirements for your transaction. + +![Transfer Reason And Supporting Docs](../images/internationalTransfer/transfer_reason_docs.jpg) + + 6. *Summary*: The summary of your transaction will be shown. If all the information is correct, you may click the **Submit** button at the bottom right hand corner of the screen. + +![Summary](../images/internationalTransfer/summary.png) + +*Note: A fixed quotation rate is created since Input sender details and will be refreshed every 30 minutes. In the case when the quotation expires, a pop up will show to fetch the latest exchange rate and a new quotation is created.* + +![Update Exchange Rate](../images/internationalTransfer/update_exchange_rate.png) + +If the available balance is insufficient for the new quotation amount, then you will be prompted to change the transfer amount or top up balance. + +![Balance Not Enough](../images/internationalTransfer/balance_not_enough.png) + + 7. *Input Password*: For security reasons, OY! will prompt clients to input their password prior to every transaction. + +![Password Filled](../images/internationalTransfer/password_filled.png) + +Each client has 5 chances to input the correct password. If you failed to input the correct password 5 times, then the transaction will automatically be cancelled. + +![Incorrect Password](../images/internationalTransfer/incorrect_password.png) + + 8. *Transaction Status: In Progress, Success, Failed.* + +*In Progress* + +Once a transaction is successfully created, it will appear in your dashboard the status column will be set as *In Progress*. + +![In Progress Status](../images/internationalTransfer/in_progress_transaction.png) + +*Success* + +Once your transaction is processed successfully, the status column will be updated as Success. Both sender and recipient should have received an email detailing a “Successful Transfer.” + +![Success Email](../images/internationalTransfer/success_email.png) + +*Failed* + +If your transaction has failed to process, the status column will be updated as Failed. Both sender and recipient should have received an email detailing a “Failed Transfer.” + +![Failed Email](../images/internationalTransfer/failed_email.png) + + 9. *Check transaction details*: You may check transaction details by clicking on the transaction id on the list of transaction details on dashboard + +![List Of Transactions](../images/internationalTransfer/list_of_transactions.png) +![Transaction Detail](../images/internationalTransfer/transaction_detail.png) + + + + +## Reimbursement +OY! Reimbursement product provides the capability to handle employee reimbursement requests and fund disbursement on one platform. Reimbursement can be done easily without any employee signing up for an OY! Account. No technical integration is required to utilize the product. + + +### Key Features + +Feature | Description +------ | ----------- +**Approval Capability** | To ensure no fraudulent requests are made, a double approval mechanism exists in the product and is mandatory for the reimbursement process. The first layer is for the reporting manager via email, and the second layer applies to the finance team via the dashboard. +**Fund Disbursement** | Not only does the product process disbursement requests, but you can also immediately schedule the disbursement time after approval from the reporting manager. Currently, the scheduled disbursement options are 1 day, 3 days, 7 days, and 14 days, allowing flexibility in managing cash flows. +**Reimbursement Details** | Every reimbursement request can be accessed through the OY! dashboard, including the uploaded file, to ensure it matches the requested amount. +**Reimbursement Tracking** | For employees, no more hassle in checking reimbursement progress with the HR or finance team. Your employee will receive a tracking email to check progress in real-time. + + + +### How to Set Approver (Reporting Manager) + +Setting up the approver will only occur once when the page is first opened. + +1. Log in to your OY! dashboard. +2. Select the "Production" environment. +3. Navigate to "Reimbursement" product under the Expense Management menu. +4. Click on "Create Reimbursement Link." +5. Choose "Register Approver." +6. Fill in the approver's name, email address, and department. +7. After registration, the approver will receive a notification via email. + + +* Approver registration page + +![Approver registration](../images/reimbursement/Approver_Registration.png) + + +**Notes** + +* Approver emails are mapped based on department names, and duplicate department names are not allowed. + +* After submission, addition, editing, or deletion of existing approvers can only be done via Customer Service. + +* Reporting managers will only receive notifications via email; no dashboard access is required. + +* The approver list view is accessible in the dashboard under reimbursement configuration. + + + + +### How to Distribute Reimbursement Link + +After Approver registration, you can start sharing the reimbursement link with employees through two methods: + +1. Via Bulk Upload: + 1. Download the sample file and input a list of employee emails in CSV or XLSX format. + 2. Upload the file for email distribution. + + +2. Via Copy Link: + 1. Copy the link and distribute it using any convenient method. + + +3. Employees will find the form link in their email and can proceed to submit a request. + + + +* Link distribution page + +![Distribution page](../images/reimbursement/Reimbursement_Link.png) + + +* Form page + +![Form page](../images/reimbursement/Reimbursement_Step1.png) + +![Form page](../images/reimbursement/Reimbursement_Step2.png) + + +**Mandatory Parameters in the Form** + +Parameter | Description +------ | ----------- +Employee Name | Employee identification purposes +Employee Email | This will be used to trigger tracking to employee post-submission +Department | Department will be mapped to approver's email directly +Bank Name | Disbursement bank name +Account Number | Disbursement bank account number. Bank account validity can be checked prior reimbursement submission +Item | Item name or description +Amount | Reimbursement total amount (in IDR). Minimum Rp 20.000 +Upload File | Placeholder to upload invoice document. Max 2 file with PDF, JPG, & PNG format (Each file max 5MB ). +Transaction Date | Date of transaction printed on the invoice + + + +**Notes** + +* You can resend the link anytime in case employees lose the email. +* A single request is applicable for 1 item only. + + + +### How to Schedule Disbursement + +Scheduled disbursement can only be done if the reporting manager has approved the request, and the dashboard admin agrees to schedule the disbursement. + +1. Open the OY! Dashboard and check the Reimbursement transaction list. +2. Requests with "Need Approval" status will require action from the dashboard side. +3. The admin can either reject with a reason or approve with a scheduled disbursement day. + + +* Request list in dashboard + +![Dashboard list](../images/reimbursement/Reimbursement_List.png) + + +* Request detail in dashboard + +![Request Detail](../images/reimbursement/Reimbursement_Detail1.png) + +![Request Detail](../images/reimbursement/Reimbursement_Detail2.png) + + +**Note:** If there is insufficient balance on the day of scheduled disbursement, you can retry manually when the balance is sufficient via the dashboard. + + + +**Dashboard Status** + +Status | Description +------ | ----------- +Pending Approval | Submitted by employee but no action yet from approver +Need Approval | Approved by approver but no action yet from admin +Canceled | Cancelation can only be performed by employee. No further action needed +Completed | Money disbursed +Rejected | Rejected by admin or approver +Scheduled Payment | After admin has approved and set scheduled payment time +Failed | Disbursement failed due to timeout or vendor issue +Insufficient Balance | Fail to disburse due to insufficient balance (admin can retry payment manually from dashboard) + + + +### How to Approve Transaction (Reporting Manager) + +1. When a new request is submitted by an employee, the respective approver will receive a notification and an approver portal link via email. +2. Inside the link, the approver can find all reimbursement requests with certain statuses (rejected, approved, and need approval). +3. The approver can choose to either reject the request with a reason or simply approve. +4. Approving the request will trigger an update inside the OY! Dashboard and employee tracker page. + + +* Approver portal (unique per approver) + +![Approver portal](../images/reimbursement/Approver_Portal_List.png) + +* Approver - request details with action buttons + +![Request details](../images/reimbursement/Approver_List_Detail.png) + + + +### How to Check Reimbursement Progress (Employee) + +1. Employees can fill in the reimbursement request form portal via email. +2. Once submitted, the employee will receive a tracking email. +3. Inside the link, employees can find real-time reimbursement progress, from the submission timestamp until disbursement timestamp. +4. Employees can still cancel the request if the approver has not yet approved. + + +* Employee Tracker page + +![Employee tracker](../images/reimbursement/Employee_Tracker1.png) + +* Employee Tracker page -- transaction detail + +![Employee tracker](../images/reimbursement/Employee_Tracker2.png) + + + +**Tracker Status** + +Status | Description +------ | ----------- +Pending Payment | Request approved but money not yet received +Scheduled Payment | After the admin has approved and set scheduled payment time +Rejected | Rejected by admin or approver +Canceled | Canceled by employee +Waiting Approval | Submitted but no action yet from approver or admin +Completed | Money has been disbursed successfully + + +**Note:** The tracking email is applicable to each employee per reimbursement request. \ No newline at end of file diff --git a/source/index.md b/source/index.md deleted file mode 100644 index 4c1fa8c9f7d..00000000000 --- a/source/index.md +++ /dev/null @@ -1,168 +0,0 @@ ---- -title: API Reference - -language_tabs: - - shell - - ruby - - python - -toc_footers: - - Sign Up for a Developer Key - - Documentation Powered by Slate - -includes: - - errors - -search: true ---- - -# Introduction - -Welcome to the Kittn API! You can use our API to access Kittn API endpoints, which can get information on various cats, kittens, and breeds in our database. - -We have language bindings in Shell, Ruby, and Python! You can view code examples in the dark area to the right, and you can switch the programming language of the examples with the tabs in the top right. - -This example API documentation page was created with [Slate](http://github.com/tripit/slate). Feel free to edit it and use it as a base for your own API's documentation. - -# Authentication - -> To authorize, use this code: - -```ruby -require 'kittn' - -api = Kittn::APIClient.authorize!('meowmeowmeow') -``` - -```python -import kittn - -api = kittn.authorize('meowmeowmeow') -``` - -```shell -# With shell, you can just pass the correct header with each request -curl "api_endpoint_here" - -H "Authorization: meowmeowmeow" -``` - -> Make sure to replace `meowmeowmeow` with your API key. - -Kittn uses API keys to allow access to the API. You can register a new Kittn API key at our [developer portal](http://example.com/developers). - -Kittn expects for the API key to be included in all API requests to the server in a header that looks like the following: - -`Authorization: meowmeowmeow` - - - -# Kittens - -## Get All Kittens - -```ruby -require 'kittn' - -api = Kittn::APIClient.authorize!('meowmeowmeow') -api.kittens.get -``` - -```python -import kittn - -api = kittn.authorize('meowmeowmeow') -api.kittens.get() -``` - -```shell -curl "http://example.com/api/kittens" - -H "Authorization: meowmeowmeow" -``` - -> The above command returns JSON structured like this: - -```json -[ - { - "id": 1, - "name": "Fluffums", - "breed": "calico", - "fluffiness": 6, - "cuteness": 7 - }, - { - "id": 2, - "name": "Isis", - "breed": "unknown", - "fluffiness": 5, - "cuteness": 10 - } -] -``` - -This endpoint retrieves all kittens. - -### HTTP Request - -`GET http://example.com/api/kittens` - -### Query Parameters - -Parameter | Default | Description ---------- | ------- | ----------- -include_cats | false | If set to true, the result will also include cats. -available | true | If set to false, the result will include kittens that have already been adopted. - - - -## Get a Specific Kitten - -```ruby -require 'kittn' - -api = Kittn::APIClient.authorize!('meowmeowmeow') -api.kittens.get(2) -``` - -```python -import kittn - -api = kittn.authorize('meowmeowmeow') -api.kittens.get(2) -``` - -```shell -curl "http://example.com/api/kittens/2" - -H "Authorization: meowmeowmeow" -``` - -> The above command returns JSON structured like this: - -```json -{ - "id": 2, - "name": "Isis", - "breed": "unknown", - "fluffiness": 5, - "cuteness": 10 -} -``` - -This endpoint retrieves a specific kitten. - - - -### HTTP Request - -`GET http://example.com/kittens/` - -### URL Parameters - -Parameter | Description ---------- | ----------- -ID | The ID of the kitten to retrieve - diff --git a/source/javascripts/all.js b/source/javascripts/all.js index ffaa9b01307..5f5d4067ba6 100644 --- a/source/javascripts/all.js +++ b/source/javascripts/all.js @@ -1,4 +1,2 @@ -//= require ./lib/_energize -//= require ./app/_lang +//= require ./all_nosearch //= require ./app/_search -//= require ./app/_toc diff --git a/source/javascripts/all_nosearch.js b/source/javascripts/all_nosearch.js index 818bc4e5095..3d4c5d41983 100644 --- a/source/javascripts/all_nosearch.js +++ b/source/javascripts/all_nosearch.js @@ -1,3 +1,39 @@ //= require ./lib/_energize -//= require ./app/_lang +//= require ./app/_copy //= require ./app/_toc +//= require ./app/_lang + +function adjustLanguageSelectorWidth() { + const elem = $('.dark-box > .lang-selector'); + elem.width(elem.parent().width()); +} + +$(function () { + loadToc($('#toc'), '.toc-link', '.toc-list-h2', '.toc-list-h3', 10); + setupLanguages($('body').data('languages')); + $('.content').imagesLoaded(function () { + window.recacheHeights(); + window.refreshToc(); + }); + + $(window).resize(function () { + adjustLanguageSelectorWidth(); + }); + + bodymovin.loadAnimation({ + container: document.getElementById('learn-more'), // required + path: '/images/lottie/learn-more.json', // required + renderer: 'svg', // required + loop: true, // optional + autoplay: true, // optional + name: "Learn more", // optional + }); + + adjustLanguageSelectorWidth(); +}); + + + +window.onpopstate = function () { + activateLanguage(getLanguageFromQueryString()); +}; diff --git a/source/javascripts/app/_copy.js b/source/javascripts/app/_copy.js new file mode 100644 index 00000000000..4dfbbb6c06d --- /dev/null +++ b/source/javascripts/app/_copy.js @@ -0,0 +1,15 @@ +function copyToClipboard(container) { + const el = document.createElement('textarea'); + el.value = container.textContent.replace(/\n$/, ''); + document.body.appendChild(el); + el.select(); + document.execCommand('copy'); + document.body.removeChild(el); +} + +function setupCodeCopy() { + $('pre.highlight').prepend('
Copy to Clipboard
'); + $('.copy-clipboard').on('click', function() { + copyToClipboard(this.parentNode.children[1]); + }); +} diff --git a/source/javascripts/app/_lang.js b/source/javascripts/app/_lang.js index 1a124bb68ae..cc5ac8b6bd8 100644 --- a/source/javascripts/app/_lang.js +++ b/source/javascripts/app/_lang.js @@ -1,3 +1,5 @@ +//= require ../lib/_jquery + /* Copyright 2008-2013 Concur Technologies, Inc. @@ -13,13 +15,14 @@ WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ -(function (global) { +;(function () { 'use strict'; var languages = []; - global.setupLanguages = setupLanguages; - global.activateLanguage = activateLanguage; + window.setupLanguages = setupLanguages; + window.activateLanguage = activateLanguage; + window.getLanguageFromQueryString = getLanguageFromQueryString; function activateLanguage(language) { if (!language) return; @@ -28,11 +31,13 @@ under the License. $(".lang-selector a").removeClass('active'); $(".lang-selector a[data-language-name='" + language + "']").addClass('active'); for (var i=0; i < languages.length; i++) { - $(".highlight." + languages[i]).hide(); + $(".highlight.tab-" + languages[i]).hide(); + $(".lang-specific." + languages[i]).hide(); } - $(".highlight." + language).show(); + $(".highlight.tab-" + language).show(); + $(".lang-specific." + language).show(); - global.toc.calculateHeights(); + window.recacheHeights(); // scroll to the new location of the position if ($(window.location.hash).get(0)) { @@ -93,7 +98,7 @@ under the License. // gets the language set in the query string function getLanguageFromQueryString() { if (location.search.length >= 1) { - var language = parseURL(location.search).language + var language = parseURL(location.search).language; if (language) { return language; } else if (jQuery.inArray(location.search.substr(1), languages) != -1) { @@ -124,11 +129,16 @@ under the License. history.pushState({}, '', '?' + generateNewQueryString(language) + '#' + hash); // save language as next default - localStorage.setItem("language", language); + if (localStorage) { + localStorage.setItem("language", language); + } } function setupLanguages(l) { - var defaultLanguage = localStorage.getItem("language"); + var defaultLanguage = null; + if (localStorage) { + defaultLanguage = localStorage.getItem("language"); + } languages = l; @@ -137,7 +147,9 @@ under the License. // the language is in the URL, so use that language! activateLanguage(presetLanguage); - localStorage.setItem("language", presetLanguage); + if (localStorage) { + localStorage.setItem("language", presetLanguage); + } } else if ((defaultLanguage !== null) && (jQuery.inArray(defaultLanguage, languages) != -1)) { // the language was the last selected one saved in localstorage, so use that language! activateLanguage(defaultLanguage); @@ -155,8 +167,5 @@ under the License. activateLanguage(language); return false; }); - window.onpopstate = function() { - activateLanguage(getLanguageFromQueryString()); - }; }); -})(window); +})(); diff --git a/source/javascripts/app/_search.js b/source/javascripts/app/_search.js index 91f38a04edf..c8789ed5649 100644 --- a/source/javascripts/app/_search.js +++ b/source/javascripts/app/_search.js @@ -1,66 +1,128 @@ //= require ../lib/_lunr +//= require ../lib/_jquery //= require ../lib/_jquery.highlight -(function () { +; (function () { 'use strict'; - var content, searchResults; + var content, searchResults, searchResultsMobile; var highlightOpts = { element: 'span', className: 'search-highlight' }; + var searchDelay = 0; + var timeoutHandle = 0; + var index; - var index = new lunr.Index(); + function populate() { + index = lunr(function () { + + this.ref('id'); + this.field('title', { boost: 10 }); + this.field('body'); + this.pipeline.add(lunr.trimmer, lunr.stopWordFilter); + var lunrConfig = this; - index.ref('id'); - index.field('title', { boost: 10 }); - index.field('body'); - index.pipeline.add(lunr.trimmer, lunr.stopWordFilter); + $('h1, h2').each(function () { + var title = $(this); + var body = title.nextUntil('h1, h2'); + lunrConfig.add({ + id: title.prop('id'), + title: title.text(), + body: body.text() + }); + }); + + }); + determineSearchDelay(); + } $(populate); $(bind); - function populate() { - $('h1, h2').each(function() { - var title = $(this); - var body = title.nextUntil('h1, h2'); - index.add({ - id: title.prop('id'), - title: title.text(), - body: body.text() - }); - }); + function determineSearchDelay() { + if (index.tokenSet.toArray().length > 5000) { + searchDelay = 300; + } } function bind() { content = $('.content'); searchResults = $('.search-results'); + searchResultsMobile = $('.search-results-mobile'); + + $('#input-search').on('keyup', function (e) { + var wait = function () { + return function (executingFunction, waitTime) { + clearTimeout(timeoutHandle); + timeoutHandle = setTimeout(executingFunction, waitTime); + }; + }(); + wait(function () { + search(e, searchResults, '#input-search'); + }, searchDelay); + }); - $('#input-search').on('keyup', search); + $('#input-search-mobile').on('keyup', function (e) { + var wait = function () { + return function (executingFunction, waitTime) { + clearTimeout(timeoutHandle); + timeoutHandle = setTimeout(executingFunction, waitTime); + }; + }(); + wait(function () { + search(e, searchResultsMobile, '#input-search-mobile'); + }, searchDelay); + }); + + $('#search-toggle-mobile').click(function () { + $('.search-bar-mobile').addClass('open') + var searchInput = $('#input-search-mobile')[0]; + searchInput.value = '' + }); + + $('#search-bar-close').click(function () { + $('.search-bar-mobile').removeClass('open') + searchResultsMobile.removeClass('visible'); + searchResultsMobile.empty(); + }) + + $('*').click(function () { + searchResultsMobile.removeClass('visible'); + searchResults.removeClass('visible'); + }) } - function search(event) { + function search(event, searchResult, idInputSearch) { + + var searchInput = $(idInputSearch)[0]; + unhighlight(); - searchResults.addClass('visible'); + searchResult.addClass('visible'); // ESC clears the field - if (event.keyCode === 27) this.value = ''; + if (event.keyCode === 27) searchInput.value = ''; - if (this.value) { - var results = index.search(this.value).filter(function(r) { + if (searchInput.value) { + var results = index.search(searchInput.value).filter(function (r) { return r.score > 0.0001; }); if (results.length) { - searchResults.empty(); + searchResult.empty(); $.each(results, function (index, result) { var elem = document.getElementById(result.ref); - searchResults.append("
  • " + $(elem).text() + "
  • "); + searchResult.append("
  • " + $(elem).text() + "
  • "); }); - highlight.call(this); + highlight.call(searchInput); + + searchResult.click(function () { + searchResult.removeClass('visible'); + }) } else { - searchResults.html('
  • '); - $('.search-results li').text('No Results Found for "' + this.value + '"'); + searchResult.empty(); + searchResult.append('
  • No Results Found for "' + searchInput.value + '"
  • '); } } else { unhighlight(); - searchResults.removeClass('visible'); + searchResult.removeClass('visible'); + searchResult.unbind('click'); } } @@ -71,4 +133,7 @@ function unhighlight() { content.unhighlight(highlightOpts); } + + })(); + diff --git a/source/javascripts/app/_toc.js b/source/javascripts/app/_toc.js index d84bf8e197a..94d1fc11782 100644 --- a/source/javascripts/app/_toc.js +++ b/source/javascripts/app/_toc.js @@ -1,50 +1,130 @@ -//= require ../lib/_jquery_ui -//= require ../lib/_jquery.tocify -(function (global) { +//= require ../lib/_jquery +//= require ../lib/_imagesloaded.min +; (function () { 'use strict'; - var closeToc = function() { - $(".tocify-wrapper").removeClass('open'); + var htmlPattern = /<[^>]*>/g; + var loaded = false; + + var debounce = function (func, waitTime) { + var timeout = false; + return function () { + if (timeout === false) { + setTimeout(function () { + func(); + timeout = false; + }, waitTime); + timeout = true; + } + }; + }; + + var closeToc = function () { + $(".toc-wrapper").removeClass('open'); $("#nav-button").removeClass('open'); }; - var makeToc = function() { - global.toc = $("#toc").tocify({ - selectors: 'h1, h2', - extendPage: false, - theme: 'none', - smoothScroll: false, - showEffectSpeed: 0, - hideEffectSpeed: 180, - ignoreSelector: '.toc-ignore', - highlightOffset: 60, - scrollTo: -1, - scrollHistory: true, - hashGenerator: function (text, element) { - return element.prop('id'); + function loadToc($toc, tocLinkSelector, tocListSelector, tocListSelectorChild, scrollOffset) { + var headerHeights = {}; + var pageHeight = 0; + var windowHeight = 0; + var originalTitle = document.title; + + var recacheHeights = function () { + headerHeights = {}; + pageHeight = $(document).height(); + windowHeight = $(window).height(); + + $toc.find(tocLinkSelector).each(function () { + var targetId = $(this).attr('href'); + if (targetId[0] === "#") { + headerHeights[targetId] = $("#" + $.escapeSelector(targetId.substring(1))).offset().top; + } + }); + }; + + var refreshToc = function () { + var currentTop = $(document).scrollTop() + scrollOffset; + + if (currentTop + windowHeight >= pageHeight) { + // at bottom of page, so just select last header by making currentTop very large + // this fixes the problem where the last header won't ever show as active if its content + // is shorter than the window height + currentTop = pageHeight + 1000; + } + + var best = null; + for (var name in headerHeights) { + if ((headerHeights[name] < currentTop && headerHeights[name] > headerHeights[best]) || best === null) { + best = name; + } } - }).data('toc-tocify'); - $("#nav-button").click(function() { - $(".tocify-wrapper").toggleClass('open'); - $("#nav-button").toggleClass('open'); - return false; - }); + // Catch the initial load case + if (currentTop == scrollOffset && !loaded) { + best = window.location.hash; + loaded = true; + } - $(".page-wrapper").click(closeToc); - $(".tocify-item").click(closeToc); - }; + var $best = $toc.find("[href='" + best + "']").first(); + if (!$best.hasClass("active")) { + // .active is applied to the ToC link we're currently on, and its parent
      s selected by tocListSelector + // .active-expanded is applied to the ToC links that are parents of this one + $toc.find(".active").removeClass("active"); + $toc.find(".active-parent").removeClass("active-parent"); + $best.addClass("active"); + $best.parents(tocListSelector).addClass("active").siblings(tocLinkSelector).addClass('active-parent'); + $best.parents(tocListSelectorChild).addClass("active").siblings(tocLinkSelector).addClass('active-parent'); + $best.siblings(tocListSelector).addClass("active"); + if ($best.siblings(tocListSelectorChild).length > 0) { + $best.addClass("active-parent"); + $best.siblings(tocListSelectorChild).addClass("active"); + } + $toc.find(tocListSelector).filter(":not(.active)").slideUp(150); + $toc.find(tocListSelector).filter(".active").slideDown(150); + $toc.find(tocListSelectorChild).filter(":not(.active)").slideUp(150); + $toc.find(tocListSelectorChild).filter(".active").slideDown(150); + if (window.history.replaceState) { + window.history.replaceState(null, "", best); + } + var thisTitle = $best.data("title"); + if (thisTitle !== undefined && thisTitle.length > 0) { + document.title = thisTitle.replace(htmlPattern, "") + " – " + originalTitle; + } else { + document.title = originalTitle; + } + } + }; - // Hack to make already open sections to start opened, - // instead of displaying an ugly animation - function animate () { - setTimeout(function() { - toc.setOption('showEffectSpeed', 180); - }, 50); - } + var makeToc = function () { + recacheHeights(); + refreshToc(); - $(makeToc); - $(animate); + $("#nav-button").click(function () { + $(".toc-wrapper").toggleClass('open'); + $("#nav-button").toggleClass('open'); + return false; + }); + $(".page-wrapper").click(closeToc); + $(".toc-link").click(closeToc); + $(".header-close").click(closeToc); -})(window); + // reload immediately after scrolling on toc click + $toc.find(tocLinkSelector).click(function () { + setTimeout(function () { + refreshToc(); + }, 0); + }); + + $(window).scroll(debounce(refreshToc, 200)); + $(window).resize(debounce(recacheHeights, 200)); + }; + + makeToc(); + + window.recacheHeights = recacheHeights; + window.refreshToc = refreshToc; + } + window.loadToc = loadToc; +})(); diff --git a/source/javascripts/lazyload.js b/source/javascripts/lazyload.js new file mode 100644 index 00000000000..35b726aa46c --- /dev/null +++ b/source/javascripts/lazyload.js @@ -0,0 +1,3 @@ +/*! lazysizes - v5.3.2 */ + +!function(e){var t=function(u,D,f){"use strict";var k,H;if(function(){var e;var t={lazyClass:"lazyload",loadedClass:"lazyloaded",loadingClass:"lazyloading",preloadClass:"lazypreload",errorClass:"lazyerror",autosizesClass:"lazyautosizes",fastLoadedClass:"ls-is-cached",iframeLoadMode:0,srcAttr:"data-src",srcsetAttr:"data-srcset",sizesAttr:"data-sizes",minSize:40,customMedia:{},init:true,expFactor:1.5,hFac:.8,loadMode:2,loadHidden:true,ricTimeout:0,throttleDelay:125};H=u.lazySizesConfig||u.lazysizesConfig||{};for(e in t){if(!(e in H)){H[e]=t[e]}}}(),!D||!D.getElementsByClassName){return{init:function(){},cfg:H,noSupport:true}}var O=D.documentElement,i=u.HTMLPictureElement,P="addEventListener",$="getAttribute",q=u[P].bind(u),I=u.setTimeout,U=u.requestAnimationFrame||I,o=u.requestIdleCallback,j=/^picture$/i,r=["load","error","lazyincluded","_lazyloaded"],a={},G=Array.prototype.forEach,J=function(e,t){if(!a[t]){a[t]=new RegExp("(\\s|^)"+t+"(\\s|$)")}return a[t].test(e[$]("class")||"")&&a[t]},K=function(e,t){if(!J(e,t)){e.setAttribute("class",(e[$]("class")||"").trim()+" "+t)}},Q=function(e,t){var a;if(a=J(e,t)){e.setAttribute("class",(e[$]("class")||"").replace(a," "))}},V=function(t,a,e){var i=e?P:"removeEventListener";if(e){V(t,a)}r.forEach(function(e){t[i](e,a)})},X=function(e,t,a,i,r){var n=D.createEvent("Event");if(!a){a={}}a.instance=k;n.initEvent(t,!i,!r);n.detail=a;e.dispatchEvent(n);return n},Y=function(e,t){var a;if(!i&&(a=u.picturefill||H.pf)){if(t&&t.src&&!e[$]("srcset")){e.setAttribute("srcset",t.src)}a({reevaluate:true,elements:[e]})}else if(t&&t.src){e.src=t.src}},Z=function(e,t){return(getComputedStyle(e,null)||{})[t]},s=function(e,t,a){a=a||e.offsetWidth;while(a49?function(){o(t,{timeout:n});if(n!==H.ricTimeout){n=H.ricTimeout}}:te(function(){I(t)},true);return function(e){var t;if(e=e===true){n=33}if(a){return}a=true;t=r-(f.now()-i);if(t<0){t=0}if(e||t<9){s()}else{I(s,t)}}},ie=function(e){var t,a;var i=99;var r=function(){t=null;e()};var n=function(){var e=f.now()-a;if(e0;if(r&&Z(i,"overflow")!="visible"){a=i.getBoundingClientRect();r=C>a.left&&pa.top-1&&g500&&O.clientWidth>500?500:370:H.expand;k._defEx=u;f=u*H.expFactor;c=H.hFac;A=null;if(w2&&h>2&&!D.hidden){w=f;N=0}else if(h>1&&N>1&&M<6){w=u}else{w=_}}if(l!==n){y=innerWidth+n*c;z=innerHeight+n;s=n*-1;l=n}a=d[t].getBoundingClientRect();if((b=a.bottom)>=s&&(g=a.top)<=z&&(C=a.right)>=s*c&&(p=a.left)<=y&&(b||C||p||g)&&(H.loadHidden||x(d[t]))&&(m&&M<3&&!o&&(h<3||N<4)||W(d[t],n))){R(d[t]);r=true;if(M>9){break}}else if(!r&&m&&!i&&M<4&&N<4&&h>2&&(v[0]||H.preloadAfterLoad)&&(v[0]||!o&&(b||C||p||g||d[t][$](H.sizesAttr)!="auto"))){i=v[0]||d[t]}}if(i&&!r){R(i)}}};var a=ae(t);var S=function(e){var t=e.target;if(t._lazyCache){delete t._lazyCache;return}L(e);K(t,H.loadedClass);Q(t,H.loadingClass);V(t,B);X(t,"lazyloaded")};var i=te(S);var B=function(e){i({target:e.target})};var T=function(e,t){var a=e.getAttribute("data-load-mode")||H.iframeLoadMode;if(a==0){e.contentWindow.location.replace(t)}else if(a==1){e.src=t}};var F=function(e){var t;var a=e[$](H.srcsetAttr);if(t=H.customMedia[e[$]("data-media")||e[$]("media")]){e.setAttribute("media",t)}if(a){e.setAttribute("srcset",a)}};var s=te(function(t,e,a,i,r){var n,s,o,l,u,f;if(!(u=X(t,"lazybeforeunveil",e)).defaultPrevented){if(i){if(a){K(t,H.autosizesClass)}else{t.setAttribute("sizes",i)}}s=t[$](H.srcsetAttr);n=t[$](H.srcAttr);if(r){o=t.parentNode;l=o&&j.test(o.nodeName||"")}f=e.firesLoad||"src"in t&&(s||n||l);u={target:t};K(t,H.loadingClass);if(f){clearTimeout(c);c=I(L,2500);V(t,B,true)}if(l){G.call(o.getElementsByTagName("source"),F)}if(s){t.setAttribute("srcset",s)}else if(n&&!l){if(d.test(t.nodeName)){T(t,n)}else{t.src=n}}if(r&&(s||l)){Y(t,{src:n})}}if(t._lazyRace){delete t._lazyRace}Q(t,H.lazyClass);ee(function(){var e=t.complete&&t.naturalWidth>1;if(!f||e){if(e){K(t,H.fastLoadedClass)}S(u);t._lazyCache=true;I(function(){if("_lazyCache"in t){delete t._lazyCache}},9)}if(t.loading=="lazy"){M--}},true)});var R=function(e){if(e._lazyRace){return}var t;var a=n.test(e.nodeName);var i=a&&(e[$](H.sizesAttr)||e[$]("sizes"));var r=i=="auto";if((r||!m)&&a&&(e[$]("src")||e.srcset)&&!e.complete&&!J(e,H.errorClass)&&J(e,H.lazyClass)){return}t=X(e,"lazyunveilread").detail;if(r){re.updateElem(e,true,e.offsetWidth)}e._lazyRace=true;M++;s(e,t,r,i,a)};var r=ie(function(){H.loadMode=3;a()});var o=function(){if(H.loadMode==3){H.loadMode=2}r()};var l=function(){if(m){return}if(f.now()-e<999){I(l,999);return}m=true;H.loadMode=3;a();q("scroll",o,true)};return{_:function(){e=f.now();k.elements=D.getElementsByClassName(H.lazyClass);v=D.getElementsByClassName(H.lazyClass+" "+H.preloadClass);q("scroll",a,true);q("resize",a,true);q("pageshow",function(e){if(e.persisted){var t=D.querySelectorAll("."+H.loadingClass);if(t.length&&t.forEach){U(function(){t.forEach(function(e){if(e.complete){R(e)}})})}}});if(u.MutationObserver){new MutationObserver(a).observe(O,{childList:true,subtree:true,attributes:true})}else{O[P]("DOMNodeInserted",a,true);O[P]("DOMAttrModified",a,true);setInterval(a,999)}q("hashchange",a,true);["focus","mouseover","click","load","transitionend","animationend"].forEach(function(e){D[P](e,a,true)});if(/d$|^c/.test(D.readyState)){l()}else{q("load",l);D[P]("DOMContentLoaded",a);I(l,2e4)}if(k.elements.length){t();ee._lsFlush()}else{a()}},checkElems:a,unveil:R,_aLSL:o}}(),re=function(){var a;var n=te(function(e,t,a,i){var r,n,s;e._lazysizesWidth=i;i+="px";e.setAttribute("sizes",i);if(j.test(t.nodeName||"")){r=t.getElementsByTagName("source");for(n=0,s=r.length;n elements + // (i.e., `typeof document.createElement( "object" ) === "function"`). + // We don't want to classify *any* DOM node as a function. + // Support: QtWeb <=3.8.5, WebKit <=534.34, wkhtmltopdf tool <=0.12.5 + // Plus for old WebKit, typeof returns "function" for HTML collections + // (e.g., `typeof document.getElementsByTagName("div") === "function"`). (gh-4756) + return typeof obj === "function" && typeof obj.nodeType !== "number" && + typeof obj.item !== "function"; + }; + + +var isWindow = function isWindow( obj ) { + return obj != null && obj === obj.window; + }; + + +var document = window.document; + + + + var preservedScriptAttributes = { + type: true, + src: true, + nonce: true, + noModule: true + }; + + function DOMEval( code, node, doc ) { + doc = doc || document; + + var i, val, + script = doc.createElement( "script" ); + + script.text = code; + if ( node ) { + for ( i in preservedScriptAttributes ) { + + // Support: Firefox 64+, Edge 18+ + // Some browsers don't support the "nonce" property on scripts. + // On the other hand, just using `getAttribute` is not enough as + // the `nonce` attribute is reset to an empty string whenever it + // becomes browsing-context connected. + // See https://github.com/whatwg/html/issues/2369 + // See https://html.spec.whatwg.org/#nonce-attributes + // The `node.getAttribute` check was added for the sake of + // `jQuery.globalEval` so that it can fake a nonce-containing node + // via an object. + val = node[ i ] || node.getAttribute && node.getAttribute( i ); + if ( val ) { + script.setAttribute( i, val ); + } + } + } + doc.head.appendChild( script ).parentNode.removeChild( script ); + } + + +function toType( obj ) { + if ( obj == null ) { + return obj + ""; + } + + // Support: Android <=2.3 only (functionish RegExp) + return typeof obj === "object" || typeof obj === "function" ? + class2type[ toString.call( obj ) ] || "object" : + typeof obj; +} +/* global Symbol */ +// Defining this global in .eslintrc.json would create a danger of using the global +// unguarded in another place, it seems safer to define global only for this module + + + +var + version = "3.6.0", + + // Define a local copy of jQuery + jQuery = function( selector, context ) { + + // The jQuery object is actually just the init constructor 'enhanced' + // Need init if jQuery is called (just allow error to be thrown if not included) + return new jQuery.fn.init( selector, context ); + }; + +jQuery.fn = jQuery.prototype = { + + // The current version of jQuery being used + jquery: version, + + constructor: jQuery, + + // The default length of a jQuery object is 0 + length: 0, + + toArray: function() { + return slice.call( this ); + }, + + // Get the Nth element in the matched element set OR + // Get the whole matched element set as a clean array + get: function( num ) { + + // Return all the elements in a clean array + if ( num == null ) { + return slice.call( this ); + } + + // Return just the one element from the set + return num < 0 ? this[ num + this.length ] : this[ num ]; + }, + + // Take an array of elements and push it onto the stack + // (returning the new matched element set) + pushStack: function( elems ) { + + // Build a new jQuery matched element set + var ret = jQuery.merge( this.constructor(), elems ); + + // Add the old object onto the stack (as a reference) + ret.prevObject = this; + + // Return the newly-formed element set + return ret; + }, + + // Execute a callback for every element in the matched set. + each: function( callback ) { + return jQuery.each( this, callback ); + }, + + map: function( callback ) { + return this.pushStack( jQuery.map( this, function( elem, i ) { + return callback.call( elem, i, elem ); + } ) ); + }, + + slice: function() { + return this.pushStack( slice.apply( this, arguments ) ); + }, + + first: function() { + return this.eq( 0 ); + }, + + last: function() { + return this.eq( -1 ); + }, + + even: function() { + return this.pushStack( jQuery.grep( this, function( _elem, i ) { + return ( i + 1 ) % 2; + } ) ); + }, + + odd: function() { + return this.pushStack( jQuery.grep( this, function( _elem, i ) { + return i % 2; + } ) ); + }, + + eq: function( i ) { + var len = this.length, + j = +i + ( i < 0 ? len : 0 ); + return this.pushStack( j >= 0 && j < len ? [ this[ j ] ] : [] ); + }, + + end: function() { + return this.prevObject || this.constructor(); + }, + + // For internal use only. + // Behaves like an Array's method, not like a jQuery method. + push: push, + sort: arr.sort, + splice: arr.splice +}; + +jQuery.extend = jQuery.fn.extend = function() { + var options, name, src, copy, copyIsArray, clone, + target = arguments[ 0 ] || {}, + i = 1, + length = arguments.length, + deep = false; + + // Handle a deep copy situation + if ( typeof target === "boolean" ) { + deep = target; + + // Skip the boolean and the target + target = arguments[ i ] || {}; + i++; + } + + // Handle case when target is a string or something (possible in deep copy) + if ( typeof target !== "object" && !isFunction( target ) ) { + target = {}; + } + + // Extend jQuery itself if only one argument is passed + if ( i === length ) { + target = this; + i--; + } + + for ( ; i < length; i++ ) { + + // Only deal with non-null/undefined values + if ( ( options = arguments[ i ] ) != null ) { + + // Extend the base object + for ( name in options ) { + copy = options[ name ]; + + // Prevent Object.prototype pollution + // Prevent never-ending loop + if ( name === "__proto__" || target === copy ) { + continue; + } + + // Recurse if we're merging plain objects or arrays + if ( deep && copy && ( jQuery.isPlainObject( copy ) || + ( copyIsArray = Array.isArray( copy ) ) ) ) { + src = target[ name ]; + + // Ensure proper type for the source value + if ( copyIsArray && !Array.isArray( src ) ) { + clone = []; + } else if ( !copyIsArray && !jQuery.isPlainObject( src ) ) { + clone = {}; + } else { + clone = src; + } + copyIsArray = false; + + // Never move original objects, clone them + target[ name ] = jQuery.extend( deep, clone, copy ); + + // Don't bring in undefined values + } else if ( copy !== undefined ) { + target[ name ] = copy; + } + } + } + } + + // Return the modified object + return target; +}; + +jQuery.extend( { + + // Unique for each copy of jQuery on the page + expando: "jQuery" + ( version + Math.random() ).replace( /\D/g, "" ), + + // Assume jQuery is ready without the ready module + isReady: true, + + error: function( msg ) { + throw new Error( msg ); + }, + + noop: function() {}, + + isPlainObject: function( obj ) { + var proto, Ctor; + + // Detect obvious negatives + // Use toString instead of jQuery.type to catch host objects + if ( !obj || toString.call( obj ) !== "[object Object]" ) { + return false; + } + + proto = getProto( obj ); + + // Objects with no prototype (e.g., `Object.create( null )`) are plain + if ( !proto ) { + return true; + } + + // Objects with prototype are plain iff they were constructed by a global Object function + Ctor = hasOwn.call( proto, "constructor" ) && proto.constructor; + return typeof Ctor === "function" && fnToString.call( Ctor ) === ObjectFunctionString; + }, + + isEmptyObject: function( obj ) { + var name; + + for ( name in obj ) { + return false; + } + return true; + }, + + // Evaluates a script in a provided context; falls back to the global one + // if not specified. + globalEval: function( code, options, doc ) { + DOMEval( code, { nonce: options && options.nonce }, doc ); + }, + + each: function( obj, callback ) { + var length, i = 0; + + if ( isArrayLike( obj ) ) { + length = obj.length; + for ( ; i < length; i++ ) { + if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) { + break; + } + } + } else { + for ( i in obj ) { + if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) { + break; + } + } + } + + return obj; + }, + + // results is for internal usage only + makeArray: function( arr, results ) { + var ret = results || []; + + if ( arr != null ) { + if ( isArrayLike( Object( arr ) ) ) { + jQuery.merge( ret, + typeof arr === "string" ? + [ arr ] : arr + ); + } else { + push.call( ret, arr ); + } + } + + return ret; + }, + + inArray: function( elem, arr, i ) { + return arr == null ? -1 : indexOf.call( arr, elem, i ); + }, + + // Support: Android <=4.0 only, PhantomJS 1 only + // push.apply(_, arraylike) throws on ancient WebKit + merge: function( first, second ) { + var len = +second.length, + j = 0, + i = first.length; + + for ( ; j < len; j++ ) { + first[ i++ ] = second[ j ]; + } + + first.length = i; + + return first; + }, + + grep: function( elems, callback, invert ) { + var callbackInverse, + matches = [], + i = 0, + length = elems.length, + callbackExpect = !invert; + + // Go through the array, only saving the items + // that pass the validator function + for ( ; i < length; i++ ) { + callbackInverse = !callback( elems[ i ], i ); + if ( callbackInverse !== callbackExpect ) { + matches.push( elems[ i ] ); + } + } + + return matches; + }, + + // arg is for internal usage only + map: function( elems, callback, arg ) { + var length, value, + i = 0, + ret = []; + + // Go through the array, translating each of the items to their new values + if ( isArrayLike( elems ) ) { + length = elems.length; + for ( ; i < length; i++ ) { + value = callback( elems[ i ], i, arg ); + + if ( value != null ) { + ret.push( value ); + } + } + + // Go through every key on the object, + } else { + for ( i in elems ) { + value = callback( elems[ i ], i, arg ); + + if ( value != null ) { + ret.push( value ); + } + } + } + + // Flatten any nested arrays + return flat( ret ); + }, + + // A global GUID counter for objects + guid: 1, + + // jQuery.support is not used in Core but other projects attach their + // properties to it so it needs to exist. + support: support +} ); + +if ( typeof Symbol === "function" ) { + jQuery.fn[ Symbol.iterator ] = arr[ Symbol.iterator ]; +} + +// Populate the class2type map +jQuery.each( "Boolean Number String Function Array Date RegExp Object Error Symbol".split( " " ), + function( _i, name ) { + class2type[ "[object " + name + "]" ] = name.toLowerCase(); + } ); + +function isArrayLike( obj ) { + + // Support: real iOS 8.2 only (not reproducible in simulator) + // `in` check used to prevent JIT error (gh-2145) + // hasOwn isn't used here due to false negatives + // regarding Nodelist length in IE + var length = !!obj && "length" in obj && obj.length, + type = toType( obj ); + + if ( isFunction( obj ) || isWindow( obj ) ) { + return false; + } + + return type === "array" || length === 0 || + typeof length === "number" && length > 0 && ( length - 1 ) in obj; +} +var Sizzle = +/*! + * Sizzle CSS Selector Engine v2.3.6 + * https://sizzlejs.com/ + * + * Copyright JS Foundation and other contributors + * Released under the MIT license + * https://js.foundation/ + * + * Date: 2021-02-16 + */ +( function( window ) { +var i, + support, + Expr, + getText, + isXML, + tokenize, + compile, + select, + outermostContext, + sortInput, + hasDuplicate, + + // Local document vars + setDocument, + document, + docElem, + documentIsHTML, + rbuggyQSA, + rbuggyMatches, + matches, + contains, + + // Instance-specific data + expando = "sizzle" + 1 * new Date(), + preferredDoc = window.document, + dirruns = 0, + done = 0, + classCache = createCache(), + tokenCache = createCache(), + compilerCache = createCache(), + nonnativeSelectorCache = createCache(), + sortOrder = function( a, b ) { + if ( a === b ) { + hasDuplicate = true; + } + return 0; + }, + + // Instance methods + hasOwn = ( {} ).hasOwnProperty, + arr = [], + pop = arr.pop, + pushNative = arr.push, + push = arr.push, + slice = arr.slice, + + // Use a stripped-down indexOf as it's faster than native + // https://jsperf.com/thor-indexof-vs-for/5 + indexOf = function( list, elem ) { + var i = 0, + len = list.length; + for ( ; i < len; i++ ) { + if ( list[ i ] === elem ) { + return i; + } + } + return -1; + }, + + booleans = "checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|" + + "ismap|loop|multiple|open|readonly|required|scoped", + + // Regular expressions + + // http://www.w3.org/TR/css3-selectors/#whitespace + whitespace = "[\\x20\\t\\r\\n\\f]", + + // https://www.w3.org/TR/css-syntax-3/#ident-token-diagram + identifier = "(?:\\\\[\\da-fA-F]{1,6}" + whitespace + + "?|\\\\[^\\r\\n\\f]|[\\w-]|[^\0-\\x7f])+", + + // Attribute selectors: http://www.w3.org/TR/selectors/#attribute-selectors + attributes = "\\[" + whitespace + "*(" + identifier + ")(?:" + whitespace + + + // Operator (capture 2) + "*([*^$|!~]?=)" + whitespace + + + // "Attribute values must be CSS identifiers [capture 5] + // or strings [capture 3 or capture 4]" + "*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|(" + identifier + "))|)" + + whitespace + "*\\]", + + pseudos = ":(" + identifier + ")(?:\\((" + + + // To reduce the number of selectors needing tokenize in the preFilter, prefer arguments: + // 1. quoted (capture 3; capture 4 or capture 5) + "('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|" + + + // 2. simple (capture 6) + "((?:\\\\.|[^\\\\()[\\]]|" + attributes + ")*)|" + + + // 3. anything else (capture 2) + ".*" + + ")\\)|)", + + // Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter + rwhitespace = new RegExp( whitespace + "+", "g" ), + rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + + whitespace + "+$", "g" ), + + rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ), + rcombinators = new RegExp( "^" + whitespace + "*([>+~]|" + whitespace + ")" + whitespace + + "*" ), + rdescend = new RegExp( whitespace + "|>" ), + + rpseudo = new RegExp( pseudos ), + ridentifier = new RegExp( "^" + identifier + "$" ), + + matchExpr = { + "ID": new RegExp( "^#(" + identifier + ")" ), + "CLASS": new RegExp( "^\\.(" + identifier + ")" ), + "TAG": new RegExp( "^(" + identifier + "|[*])" ), + "ATTR": new RegExp( "^" + attributes ), + "PSEUDO": new RegExp( "^" + pseudos ), + "CHILD": new RegExp( "^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" + + whitespace + "*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + + whitespace + "*(\\d+)|))" + whitespace + "*\\)|)", "i" ), + "bool": new RegExp( "^(?:" + booleans + ")$", "i" ), + + // For use in libraries implementing .is() + // We use this for POS matching in `select` + "needsContext": new RegExp( "^" + whitespace + + "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" + whitespace + + "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" ) + }, + + rhtml = /HTML$/i, + rinputs = /^(?:input|select|textarea|button)$/i, + rheader = /^h\d$/i, + + rnative = /^[^{]+\{\s*\[native \w/, + + // Easily-parseable/retrievable ID or TAG or CLASS selectors + rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/, + + rsibling = /[+~]/, + + // CSS escapes + // http://www.w3.org/TR/CSS21/syndata.html#escaped-characters + runescape = new RegExp( "\\\\[\\da-fA-F]{1,6}" + whitespace + "?|\\\\([^\\r\\n\\f])", "g" ), + funescape = function( escape, nonHex ) { + var high = "0x" + escape.slice( 1 ) - 0x10000; + + return nonHex ? + + // Strip the backslash prefix from a non-hex escape sequence + nonHex : + + // Replace a hexadecimal escape sequence with the encoded Unicode code point + // Support: IE <=11+ + // For values outside the Basic Multilingual Plane (BMP), manually construct a + // surrogate pair + high < 0 ? + String.fromCharCode( high + 0x10000 ) : + String.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 ); + }, + + // CSS string/identifier serialization + // https://drafts.csswg.org/cssom/#common-serializing-idioms + rcssescape = /([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g, + fcssescape = function( ch, asCodePoint ) { + if ( asCodePoint ) { + + // U+0000 NULL becomes U+FFFD REPLACEMENT CHARACTER + if ( ch === "\0" ) { + return "\uFFFD"; + } + + // Control characters and (dependent upon position) numbers get escaped as code points + return ch.slice( 0, -1 ) + "\\" + + ch.charCodeAt( ch.length - 1 ).toString( 16 ) + " "; + } + + // Other potentially-special ASCII characters get backslash-escaped + return "\\" + ch; + }, + + // Used for iframes + // See setDocument() + // Removing the function wrapper causes a "Permission Denied" + // error in IE + unloadHandler = function() { + setDocument(); + }, + + inDisabledFieldset = addCombinator( + function( elem ) { + return elem.disabled === true && elem.nodeName.toLowerCase() === "fieldset"; + }, + { dir: "parentNode", next: "legend" } + ); + +// Optimize for push.apply( _, NodeList ) +try { + push.apply( + ( arr = slice.call( preferredDoc.childNodes ) ), + preferredDoc.childNodes + ); + + // Support: Android<4.0 + // Detect silently failing push.apply + // eslint-disable-next-line no-unused-expressions + arr[ preferredDoc.childNodes.length ].nodeType; +} catch ( e ) { + push = { apply: arr.length ? + + // Leverage slice if possible + function( target, els ) { + pushNative.apply( target, slice.call( els ) ); + } : + + // Support: IE<9 + // Otherwise append directly + function( target, els ) { + var j = target.length, + i = 0; + + // Can't trust NodeList.length + while ( ( target[ j++ ] = els[ i++ ] ) ) {} + target.length = j - 1; + } + }; +} + +function Sizzle( selector, context, results, seed ) { + var m, i, elem, nid, match, groups, newSelector, + newContext = context && context.ownerDocument, + + // nodeType defaults to 9, since context defaults to document + nodeType = context ? context.nodeType : 9; + + results = results || []; + + // Return early from calls with invalid selector or context + if ( typeof selector !== "string" || !selector || + nodeType !== 1 && nodeType !== 9 && nodeType !== 11 ) { + + return results; + } + + // Try to shortcut find operations (as opposed to filters) in HTML documents + if ( !seed ) { + setDocument( context ); + context = context || document; + + if ( documentIsHTML ) { + + // If the selector is sufficiently simple, try using a "get*By*" DOM method + // (excepting DocumentFragment context, where the methods don't exist) + if ( nodeType !== 11 && ( match = rquickExpr.exec( selector ) ) ) { + + // ID selector + if ( ( m = match[ 1 ] ) ) { + + // Document context + if ( nodeType === 9 ) { + if ( ( elem = context.getElementById( m ) ) ) { + + // Support: IE, Opera, Webkit + // TODO: identify versions + // getElementById can match elements by name instead of ID + if ( elem.id === m ) { + results.push( elem ); + return results; + } + } else { + return results; + } + + // Element context + } else { + + // Support: IE, Opera, Webkit + // TODO: identify versions + // getElementById can match elements by name instead of ID + if ( newContext && ( elem = newContext.getElementById( m ) ) && + contains( context, elem ) && + elem.id === m ) { + + results.push( elem ); + return results; + } + } + + // Type selector + } else if ( match[ 2 ] ) { + push.apply( results, context.getElementsByTagName( selector ) ); + return results; + + // Class selector + } else if ( ( m = match[ 3 ] ) && support.getElementsByClassName && + context.getElementsByClassName ) { + + push.apply( results, context.getElementsByClassName( m ) ); + return results; + } + } + + // Take advantage of querySelectorAll + if ( support.qsa && + !nonnativeSelectorCache[ selector + " " ] && + ( !rbuggyQSA || !rbuggyQSA.test( selector ) ) && + + // Support: IE 8 only + // Exclude object elements + ( nodeType !== 1 || context.nodeName.toLowerCase() !== "object" ) ) { + + newSelector = selector; + newContext = context; + + // qSA considers elements outside a scoping root when evaluating child or + // descendant combinators, which is not what we want. + // In such cases, we work around the behavior by prefixing every selector in the + // list with an ID selector referencing the scope context. + // The technique has to be used as well when a leading combinator is used + // as such selectors are not recognized by querySelectorAll. + // Thanks to Andrew Dupont for this technique. + if ( nodeType === 1 && + ( rdescend.test( selector ) || rcombinators.test( selector ) ) ) { + + // Expand context for sibling selectors + newContext = rsibling.test( selector ) && testContext( context.parentNode ) || + context; + + // We can use :scope instead of the ID hack if the browser + // supports it & if we're not changing the context. + if ( newContext !== context || !support.scope ) { + + // Capture the context ID, setting it first if necessary + if ( ( nid = context.getAttribute( "id" ) ) ) { + nid = nid.replace( rcssescape, fcssescape ); + } else { + context.setAttribute( "id", ( nid = expando ) ); + } + } + + // Prefix every selector in the list + groups = tokenize( selector ); + i = groups.length; + while ( i-- ) { + groups[ i ] = ( nid ? "#" + nid : ":scope" ) + " " + + toSelector( groups[ i ] ); + } + newSelector = groups.join( "," ); + } + + try { + push.apply( results, + newContext.querySelectorAll( newSelector ) + ); + return results; + } catch ( qsaError ) { + nonnativeSelectorCache( selector, true ); + } finally { + if ( nid === expando ) { + context.removeAttribute( "id" ); + } + } + } + } + } + + // All others + return select( selector.replace( rtrim, "$1" ), context, results, seed ); +} + +/** + * Create key-value caches of limited size + * @returns {function(string, object)} Returns the Object data after storing it on itself with + * property name the (space-suffixed) string and (if the cache is larger than Expr.cacheLength) + * deleting the oldest entry + */ +function createCache() { + var keys = []; + + function cache( key, value ) { + + // Use (key + " ") to avoid collision with native prototype properties (see Issue #157) + if ( keys.push( key + " " ) > Expr.cacheLength ) { + + // Only keep the most recent entries + delete cache[ keys.shift() ]; + } + return ( cache[ key + " " ] = value ); + } + return cache; +} + +/** + * Mark a function for special use by Sizzle + * @param {Function} fn The function to mark + */ +function markFunction( fn ) { + fn[ expando ] = true; + return fn; +} + +/** + * Support testing using an element + * @param {Function} fn Passed the created element and returns a boolean result + */ +function assert( fn ) { + var el = document.createElement( "fieldset" ); + + try { + return !!fn( el ); + } catch ( e ) { + return false; + } finally { + + // Remove from its parent by default + if ( el.parentNode ) { + el.parentNode.removeChild( el ); + } + + // release memory in IE + el = null; + } +} + +/** + * Adds the same handler for all of the specified attrs + * @param {String} attrs Pipe-separated list of attributes + * @param {Function} handler The method that will be applied + */ +function addHandle( attrs, handler ) { + var arr = attrs.split( "|" ), + i = arr.length; + + while ( i-- ) { + Expr.attrHandle[ arr[ i ] ] = handler; + } +} + +/** + * Checks document order of two siblings + * @param {Element} a + * @param {Element} b + * @returns {Number} Returns less than 0 if a precedes b, greater than 0 if a follows b + */ +function siblingCheck( a, b ) { + var cur = b && a, + diff = cur && a.nodeType === 1 && b.nodeType === 1 && + a.sourceIndex - b.sourceIndex; + + // Use IE sourceIndex if available on both nodes + if ( diff ) { + return diff; + } + + // Check if b follows a + if ( cur ) { + while ( ( cur = cur.nextSibling ) ) { + if ( cur === b ) { + return -1; + } + } + } + + return a ? 1 : -1; +} + +/** + * Returns a function to use in pseudos for input types + * @param {String} type + */ +function createInputPseudo( type ) { + return function( elem ) { + var name = elem.nodeName.toLowerCase(); + return name === "input" && elem.type === type; + }; +} + +/** + * Returns a function to use in pseudos for buttons + * @param {String} type + */ +function createButtonPseudo( type ) { + return function( elem ) { + var name = elem.nodeName.toLowerCase(); + return ( name === "input" || name === "button" ) && elem.type === type; + }; +} + +/** + * Returns a function to use in pseudos for :enabled/:disabled + * @param {Boolean} disabled true for :disabled; false for :enabled + */ +function createDisabledPseudo( disabled ) { + + // Known :disabled false positives: fieldset[disabled] > legend:nth-of-type(n+2) :can-disable + return function( elem ) { + + // Only certain elements can match :enabled or :disabled + // https://html.spec.whatwg.org/multipage/scripting.html#selector-enabled + // https://html.spec.whatwg.org/multipage/scripting.html#selector-disabled + if ( "form" in elem ) { + + // Check for inherited disabledness on relevant non-disabled elements: + // * listed form-associated elements in a disabled fieldset + // https://html.spec.whatwg.org/multipage/forms.html#category-listed + // https://html.spec.whatwg.org/multipage/forms.html#concept-fe-disabled + // * option elements in a disabled optgroup + // https://html.spec.whatwg.org/multipage/forms.html#concept-option-disabled + // All such elements have a "form" property. + if ( elem.parentNode && elem.disabled === false ) { + + // Option elements defer to a parent optgroup if present + if ( "label" in elem ) { + if ( "label" in elem.parentNode ) { + return elem.parentNode.disabled === disabled; + } else { + return elem.disabled === disabled; + } + } + + // Support: IE 6 - 11 + // Use the isDisabled shortcut property to check for disabled fieldset ancestors + return elem.isDisabled === disabled || + + // Where there is no isDisabled, check manually + /* jshint -W018 */ + elem.isDisabled !== !disabled && + inDisabledFieldset( elem ) === disabled; + } + + return elem.disabled === disabled; + + // Try to winnow out elements that can't be disabled before trusting the disabled property. + // Some victims get caught in our net (label, legend, menu, track), but it shouldn't + // even exist on them, let alone have a boolean value. + } else if ( "label" in elem ) { + return elem.disabled === disabled; + } + + // Remaining elements are neither :enabled nor :disabled + return false; + }; +} + +/** + * Returns a function to use in pseudos for positionals + * @param {Function} fn + */ +function createPositionalPseudo( fn ) { + return markFunction( function( argument ) { + argument = +argument; + return markFunction( function( seed, matches ) { + var j, + matchIndexes = fn( [], seed.length, argument ), + i = matchIndexes.length; + + // Match elements found at the specified indexes + while ( i-- ) { + if ( seed[ ( j = matchIndexes[ i ] ) ] ) { + seed[ j ] = !( matches[ j ] = seed[ j ] ); + } + } + } ); + } ); +} + +/** + * Checks a node for validity as a Sizzle context + * @param {Element|Object=} context + * @returns {Element|Object|Boolean} The input node if acceptable, otherwise a falsy value + */ +function testContext( context ) { + return context && typeof context.getElementsByTagName !== "undefined" && context; +} + +// Expose support vars for convenience +support = Sizzle.support = {}; + +/** + * Detects XML nodes + * @param {Element|Object} elem An element or a document + * @returns {Boolean} True iff elem is a non-HTML XML node + */ +isXML = Sizzle.isXML = function( elem ) { + var namespace = elem && elem.namespaceURI, + docElem = elem && ( elem.ownerDocument || elem ).documentElement; + + // Support: IE <=8 + // Assume HTML when documentElement doesn't yet exist, such as inside loading iframes + // https://bugs.jquery.com/ticket/4833 + return !rhtml.test( namespace || docElem && docElem.nodeName || "HTML" ); +}; + +/** + * Sets document-related variables once based on the current document + * @param {Element|Object} [doc] An element or document object to use to set the document + * @returns {Object} Returns the current document + */ +setDocument = Sizzle.setDocument = function( node ) { + var hasCompare, subWindow, + doc = node ? node.ownerDocument || node : preferredDoc; + + // Return early if doc is invalid or already selected + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( doc == document || doc.nodeType !== 9 || !doc.documentElement ) { + return document; + } + + // Update global variables + document = doc; + docElem = document.documentElement; + documentIsHTML = !isXML( document ); + + // Support: IE 9 - 11+, Edge 12 - 18+ + // Accessing iframe documents after unload throws "permission denied" errors (jQuery #13936) + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( preferredDoc != document && + ( subWindow = document.defaultView ) && subWindow.top !== subWindow ) { + + // Support: IE 11, Edge + if ( subWindow.addEventListener ) { + subWindow.addEventListener( "unload", unloadHandler, false ); + + // Support: IE 9 - 10 only + } else if ( subWindow.attachEvent ) { + subWindow.attachEvent( "onunload", unloadHandler ); + } + } + + // Support: IE 8 - 11+, Edge 12 - 18+, Chrome <=16 - 25 only, Firefox <=3.6 - 31 only, + // Safari 4 - 5 only, Opera <=11.6 - 12.x only + // IE/Edge & older browsers don't support the :scope pseudo-class. + // Support: Safari 6.0 only + // Safari 6.0 supports :scope but it's an alias of :root there. + support.scope = assert( function( el ) { + docElem.appendChild( el ).appendChild( document.createElement( "div" ) ); + return typeof el.querySelectorAll !== "undefined" && + !el.querySelectorAll( ":scope fieldset div" ).length; + } ); + + /* Attributes + ---------------------------------------------------------------------- */ + + // Support: IE<8 + // Verify that getAttribute really returns attributes and not properties + // (excepting IE8 booleans) + support.attributes = assert( function( el ) { + el.className = "i"; + return !el.getAttribute( "className" ); + } ); + + /* getElement(s)By* + ---------------------------------------------------------------------- */ + + // Check if getElementsByTagName("*") returns only elements + support.getElementsByTagName = assert( function( el ) { + el.appendChild( document.createComment( "" ) ); + return !el.getElementsByTagName( "*" ).length; + } ); + + // Support: IE<9 + support.getElementsByClassName = rnative.test( document.getElementsByClassName ); + + // Support: IE<10 + // Check if getElementById returns elements by name + // The broken getElementById methods don't pick up programmatically-set names, + // so use a roundabout getElementsByName test + support.getById = assert( function( el ) { + docElem.appendChild( el ).id = expando; + return !document.getElementsByName || !document.getElementsByName( expando ).length; + } ); + + // ID filter and find + if ( support.getById ) { + Expr.filter[ "ID" ] = function( id ) { + var attrId = id.replace( runescape, funescape ); + return function( elem ) { + return elem.getAttribute( "id" ) === attrId; + }; + }; + Expr.find[ "ID" ] = function( id, context ) { + if ( typeof context.getElementById !== "undefined" && documentIsHTML ) { + var elem = context.getElementById( id ); + return elem ? [ elem ] : []; + } + }; + } else { + Expr.filter[ "ID" ] = function( id ) { + var attrId = id.replace( runescape, funescape ); + return function( elem ) { + var node = typeof elem.getAttributeNode !== "undefined" && + elem.getAttributeNode( "id" ); + return node && node.value === attrId; + }; + }; + + // Support: IE 6 - 7 only + // getElementById is not reliable as a find shortcut + Expr.find[ "ID" ] = function( id, context ) { + if ( typeof context.getElementById !== "undefined" && documentIsHTML ) { + var node, i, elems, + elem = context.getElementById( id ); + + if ( elem ) { + + // Verify the id attribute + node = elem.getAttributeNode( "id" ); + if ( node && node.value === id ) { + return [ elem ]; + } + + // Fall back on getElementsByName + elems = context.getElementsByName( id ); + i = 0; + while ( ( elem = elems[ i++ ] ) ) { + node = elem.getAttributeNode( "id" ); + if ( node && node.value === id ) { + return [ elem ]; + } + } + } + + return []; + } + }; + } + + // Tag + Expr.find[ "TAG" ] = support.getElementsByTagName ? + function( tag, context ) { + if ( typeof context.getElementsByTagName !== "undefined" ) { + return context.getElementsByTagName( tag ); + + // DocumentFragment nodes don't have gEBTN + } else if ( support.qsa ) { + return context.querySelectorAll( tag ); + } + } : + + function( tag, context ) { + var elem, + tmp = [], + i = 0, + + // By happy coincidence, a (broken) gEBTN appears on DocumentFragment nodes too + results = context.getElementsByTagName( tag ); + + // Filter out possible comments + if ( tag === "*" ) { + while ( ( elem = results[ i++ ] ) ) { + if ( elem.nodeType === 1 ) { + tmp.push( elem ); + } + } + + return tmp; + } + return results; + }; + + // Class + Expr.find[ "CLASS" ] = support.getElementsByClassName && function( className, context ) { + if ( typeof context.getElementsByClassName !== "undefined" && documentIsHTML ) { + return context.getElementsByClassName( className ); + } + }; + + /* QSA/matchesSelector + ---------------------------------------------------------------------- */ + + // QSA and matchesSelector support + + // matchesSelector(:active) reports false when true (IE9/Opera 11.5) + rbuggyMatches = []; + + // qSa(:focus) reports false when true (Chrome 21) + // We allow this because of a bug in IE8/9 that throws an error + // whenever `document.activeElement` is accessed on an iframe + // So, we allow :focus to pass through QSA all the time to avoid the IE error + // See https://bugs.jquery.com/ticket/13378 + rbuggyQSA = []; + + if ( ( support.qsa = rnative.test( document.querySelectorAll ) ) ) { + + // Build QSA regex + // Regex strategy adopted from Diego Perini + assert( function( el ) { + + var input; + + // Select is set to empty string on purpose + // This is to test IE's treatment of not explicitly + // setting a boolean content attribute, + // since its presence should be enough + // https://bugs.jquery.com/ticket/12359 + docElem.appendChild( el ).innerHTML = "" + + ""; + + // Support: IE8, Opera 11-12.16 + // Nothing should be selected when empty strings follow ^= or $= or *= + // The test attribute must be unknown in Opera but "safe" for WinRT + // https://msdn.microsoft.com/en-us/library/ie/hh465388.aspx#attribute_section + if ( el.querySelectorAll( "[msallowcapture^='']" ).length ) { + rbuggyQSA.push( "[*^$]=" + whitespace + "*(?:''|\"\")" ); + } + + // Support: IE8 + // Boolean attributes and "value" are not treated correctly + if ( !el.querySelectorAll( "[selected]" ).length ) { + rbuggyQSA.push( "\\[" + whitespace + "*(?:value|" + booleans + ")" ); + } + + // Support: Chrome<29, Android<4.4, Safari<7.0+, iOS<7.0+, PhantomJS<1.9.8+ + if ( !el.querySelectorAll( "[id~=" + expando + "-]" ).length ) { + rbuggyQSA.push( "~=" ); + } + + // Support: IE 11+, Edge 15 - 18+ + // IE 11/Edge don't find elements on a `[name='']` query in some cases. + // Adding a temporary attribute to the document before the selection works + // around the issue. + // Interestingly, IE 10 & older don't seem to have the issue. + input = document.createElement( "input" ); + input.setAttribute( "name", "" ); + el.appendChild( input ); + if ( !el.querySelectorAll( "[name='']" ).length ) { + rbuggyQSA.push( "\\[" + whitespace + "*name" + whitespace + "*=" + + whitespace + "*(?:''|\"\")" ); + } + + // Webkit/Opera - :checked should return selected option elements + // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked + // IE8 throws error here and will not see later tests + if ( !el.querySelectorAll( ":checked" ).length ) { + rbuggyQSA.push( ":checked" ); + } + + // Support: Safari 8+, iOS 8+ + // https://bugs.webkit.org/show_bug.cgi?id=136851 + // In-page `selector#id sibling-combinator selector` fails + if ( !el.querySelectorAll( "a#" + expando + "+*" ).length ) { + rbuggyQSA.push( ".#.+[+~]" ); + } + + // Support: Firefox <=3.6 - 5 only + // Old Firefox doesn't throw on a badly-escaped identifier. + el.querySelectorAll( "\\\f" ); + rbuggyQSA.push( "[\\r\\n\\f]" ); + } ); + + assert( function( el ) { + el.innerHTML = "" + + ""; + + // Support: Windows 8 Native Apps + // The type and name attributes are restricted during .innerHTML assignment + var input = document.createElement( "input" ); + input.setAttribute( "type", "hidden" ); + el.appendChild( input ).setAttribute( "name", "D" ); + + // Support: IE8 + // Enforce case-sensitivity of name attribute + if ( el.querySelectorAll( "[name=d]" ).length ) { + rbuggyQSA.push( "name" + whitespace + "*[*^$|!~]?=" ); + } + + // FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled) + // IE8 throws error here and will not see later tests + if ( el.querySelectorAll( ":enabled" ).length !== 2 ) { + rbuggyQSA.push( ":enabled", ":disabled" ); + } + + // Support: IE9-11+ + // IE's :disabled selector does not pick up the children of disabled fieldsets + docElem.appendChild( el ).disabled = true; + if ( el.querySelectorAll( ":disabled" ).length !== 2 ) { + rbuggyQSA.push( ":enabled", ":disabled" ); + } + + // Support: Opera 10 - 11 only + // Opera 10-11 does not throw on post-comma invalid pseudos + el.querySelectorAll( "*,:x" ); + rbuggyQSA.push( ",.*:" ); + } ); + } + + if ( ( support.matchesSelector = rnative.test( ( matches = docElem.matches || + docElem.webkitMatchesSelector || + docElem.mozMatchesSelector || + docElem.oMatchesSelector || + docElem.msMatchesSelector ) ) ) ) { + + assert( function( el ) { + + // Check to see if it's possible to do matchesSelector + // on a disconnected node (IE 9) + support.disconnectedMatch = matches.call( el, "*" ); + + // This should fail with an exception + // Gecko does not error, returns false instead + matches.call( el, "[s!='']:x" ); + rbuggyMatches.push( "!=", pseudos ); + } ); + } + + rbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join( "|" ) ); + rbuggyMatches = rbuggyMatches.length && new RegExp( rbuggyMatches.join( "|" ) ); + + /* Contains + ---------------------------------------------------------------------- */ + hasCompare = rnative.test( docElem.compareDocumentPosition ); + + // Element contains another + // Purposefully self-exclusive + // As in, an element does not contain itself + contains = hasCompare || rnative.test( docElem.contains ) ? + function( a, b ) { + var adown = a.nodeType === 9 ? a.documentElement : a, + bup = b && b.parentNode; + return a === bup || !!( bup && bup.nodeType === 1 && ( + adown.contains ? + adown.contains( bup ) : + a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16 + ) ); + } : + function( a, b ) { + if ( b ) { + while ( ( b = b.parentNode ) ) { + if ( b === a ) { + return true; + } + } + } + return false; + }; + + /* Sorting + ---------------------------------------------------------------------- */ + + // Document order sorting + sortOrder = hasCompare ? + function( a, b ) { + + // Flag for duplicate removal + if ( a === b ) { + hasDuplicate = true; + return 0; + } + + // Sort on method existence if only one input has compareDocumentPosition + var compare = !a.compareDocumentPosition - !b.compareDocumentPosition; + if ( compare ) { + return compare; + } + + // Calculate position if both inputs belong to the same document + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + compare = ( a.ownerDocument || a ) == ( b.ownerDocument || b ) ? + a.compareDocumentPosition( b ) : + + // Otherwise we know they are disconnected + 1; + + // Disconnected nodes + if ( compare & 1 || + ( !support.sortDetached && b.compareDocumentPosition( a ) === compare ) ) { + + // Choose the first element that is related to our preferred document + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( a == document || a.ownerDocument == preferredDoc && + contains( preferredDoc, a ) ) { + return -1; + } + + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( b == document || b.ownerDocument == preferredDoc && + contains( preferredDoc, b ) ) { + return 1; + } + + // Maintain original order + return sortInput ? + ( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) : + 0; + } + + return compare & 4 ? -1 : 1; + } : + function( a, b ) { + + // Exit early if the nodes are identical + if ( a === b ) { + hasDuplicate = true; + return 0; + } + + var cur, + i = 0, + aup = a.parentNode, + bup = b.parentNode, + ap = [ a ], + bp = [ b ]; + + // Parentless nodes are either documents or disconnected + if ( !aup || !bup ) { + + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + /* eslint-disable eqeqeq */ + return a == document ? -1 : + b == document ? 1 : + /* eslint-enable eqeqeq */ + aup ? -1 : + bup ? 1 : + sortInput ? + ( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) : + 0; + + // If the nodes are siblings, we can do a quick check + } else if ( aup === bup ) { + return siblingCheck( a, b ); + } + + // Otherwise we need full lists of their ancestors for comparison + cur = a; + while ( ( cur = cur.parentNode ) ) { + ap.unshift( cur ); + } + cur = b; + while ( ( cur = cur.parentNode ) ) { + bp.unshift( cur ); + } + + // Walk down the tree looking for a discrepancy + while ( ap[ i ] === bp[ i ] ) { + i++; + } + + return i ? + + // Do a sibling check if the nodes have a common ancestor + siblingCheck( ap[ i ], bp[ i ] ) : + + // Otherwise nodes in our document sort first + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + /* eslint-disable eqeqeq */ + ap[ i ] == preferredDoc ? -1 : + bp[ i ] == preferredDoc ? 1 : + /* eslint-enable eqeqeq */ + 0; + }; + + return document; +}; + +Sizzle.matches = function( expr, elements ) { + return Sizzle( expr, null, null, elements ); +}; + +Sizzle.matchesSelector = function( elem, expr ) { + setDocument( elem ); + + if ( support.matchesSelector && documentIsHTML && + !nonnativeSelectorCache[ expr + " " ] && + ( !rbuggyMatches || !rbuggyMatches.test( expr ) ) && + ( !rbuggyQSA || !rbuggyQSA.test( expr ) ) ) { + + try { + var ret = matches.call( elem, expr ); + + // IE 9's matchesSelector returns false on disconnected nodes + if ( ret || support.disconnectedMatch || + + // As well, disconnected nodes are said to be in a document + // fragment in IE 9 + elem.document && elem.document.nodeType !== 11 ) { + return ret; + } + } catch ( e ) { + nonnativeSelectorCache( expr, true ); + } + } + + return Sizzle( expr, document, null, [ elem ] ).length > 0; +}; + +Sizzle.contains = function( context, elem ) { + + // Set document vars if needed + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( ( context.ownerDocument || context ) != document ) { + setDocument( context ); + } + return contains( context, elem ); +}; + +Sizzle.attr = function( elem, name ) { + + // Set document vars if needed + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( ( elem.ownerDocument || elem ) != document ) { + setDocument( elem ); + } + + var fn = Expr.attrHandle[ name.toLowerCase() ], + + // Don't get fooled by Object.prototype properties (jQuery #13807) + val = fn && hasOwn.call( Expr.attrHandle, name.toLowerCase() ) ? + fn( elem, name, !documentIsHTML ) : + undefined; + + return val !== undefined ? + val : + support.attributes || !documentIsHTML ? + elem.getAttribute( name ) : + ( val = elem.getAttributeNode( name ) ) && val.specified ? + val.value : + null; +}; + +Sizzle.escape = function( sel ) { + return ( sel + "" ).replace( rcssescape, fcssescape ); +}; + +Sizzle.error = function( msg ) { + throw new Error( "Syntax error, unrecognized expression: " + msg ); +}; + +/** + * Document sorting and removing duplicates + * @param {ArrayLike} results + */ +Sizzle.uniqueSort = function( results ) { + var elem, + duplicates = [], + j = 0, + i = 0; + + // Unless we *know* we can detect duplicates, assume their presence + hasDuplicate = !support.detectDuplicates; + sortInput = !support.sortStable && results.slice( 0 ); + results.sort( sortOrder ); + + if ( hasDuplicate ) { + while ( ( elem = results[ i++ ] ) ) { + if ( elem === results[ i ] ) { + j = duplicates.push( i ); + } + } + while ( j-- ) { + results.splice( duplicates[ j ], 1 ); + } + } + + // Clear input after sorting to release objects + // See https://github.com/jquery/sizzle/pull/225 + sortInput = null; + + return results; +}; + +/** + * Utility function for retrieving the text value of an array of DOM nodes + * @param {Array|Element} elem + */ +getText = Sizzle.getText = function( elem ) { + var node, + ret = "", + i = 0, + nodeType = elem.nodeType; + + if ( !nodeType ) { + + // If no nodeType, this is expected to be an array + while ( ( node = elem[ i++ ] ) ) { + + // Do not traverse comment nodes + ret += getText( node ); + } + } else if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) { + + // Use textContent for elements + // innerText usage removed for consistency of new lines (jQuery #11153) + if ( typeof elem.textContent === "string" ) { + return elem.textContent; + } else { + + // Traverse its children + for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { + ret += getText( elem ); + } + } + } else if ( nodeType === 3 || nodeType === 4 ) { + return elem.nodeValue; + } + + // Do not include comment or processing instruction nodes + + return ret; +}; + +Expr = Sizzle.selectors = { + + // Can be adjusted by the user + cacheLength: 50, + + createPseudo: markFunction, + + match: matchExpr, + + attrHandle: {}, + + find: {}, + + relative: { + ">": { dir: "parentNode", first: true }, + " ": { dir: "parentNode" }, + "+": { dir: "previousSibling", first: true }, + "~": { dir: "previousSibling" } + }, + + preFilter: { + "ATTR": function( match ) { + match[ 1 ] = match[ 1 ].replace( runescape, funescape ); + + // Move the given value to match[3] whether quoted or unquoted + match[ 3 ] = ( match[ 3 ] || match[ 4 ] || + match[ 5 ] || "" ).replace( runescape, funescape ); + + if ( match[ 2 ] === "~=" ) { + match[ 3 ] = " " + match[ 3 ] + " "; + } + + return match.slice( 0, 4 ); + }, + + "CHILD": function( match ) { + + /* matches from matchExpr["CHILD"] + 1 type (only|nth|...) + 2 what (child|of-type) + 3 argument (even|odd|\d*|\d*n([+-]\d+)?|...) + 4 xn-component of xn+y argument ([+-]?\d*n|) + 5 sign of xn-component + 6 x of xn-component + 7 sign of y-component + 8 y of y-component + */ + match[ 1 ] = match[ 1 ].toLowerCase(); + + if ( match[ 1 ].slice( 0, 3 ) === "nth" ) { + + // nth-* requires argument + if ( !match[ 3 ] ) { + Sizzle.error( match[ 0 ] ); + } + + // numeric x and y parameters for Expr.filter.CHILD + // remember that false/true cast respectively to 0/1 + match[ 4 ] = +( match[ 4 ] ? + match[ 5 ] + ( match[ 6 ] || 1 ) : + 2 * ( match[ 3 ] === "even" || match[ 3 ] === "odd" ) ); + match[ 5 ] = +( ( match[ 7 ] + match[ 8 ] ) || match[ 3 ] === "odd" ); + + // other types prohibit arguments + } else if ( match[ 3 ] ) { + Sizzle.error( match[ 0 ] ); + } + + return match; + }, + + "PSEUDO": function( match ) { + var excess, + unquoted = !match[ 6 ] && match[ 2 ]; + + if ( matchExpr[ "CHILD" ].test( match[ 0 ] ) ) { + return null; + } + + // Accept quoted arguments as-is + if ( match[ 3 ] ) { + match[ 2 ] = match[ 4 ] || match[ 5 ] || ""; + + // Strip excess characters from unquoted arguments + } else if ( unquoted && rpseudo.test( unquoted ) && + + // Get excess from tokenize (recursively) + ( excess = tokenize( unquoted, true ) ) && + + // advance to the next closing parenthesis + ( excess = unquoted.indexOf( ")", unquoted.length - excess ) - unquoted.length ) ) { + + // excess is a negative index + match[ 0 ] = match[ 0 ].slice( 0, excess ); + match[ 2 ] = unquoted.slice( 0, excess ); + } + + // Return only captures needed by the pseudo filter method (type and argument) + return match.slice( 0, 3 ); + } + }, + + filter: { + + "TAG": function( nodeNameSelector ) { + var nodeName = nodeNameSelector.replace( runescape, funescape ).toLowerCase(); + return nodeNameSelector === "*" ? + function() { + return true; + } : + function( elem ) { + return elem.nodeName && elem.nodeName.toLowerCase() === nodeName; + }; + }, + + "CLASS": function( className ) { + var pattern = classCache[ className + " " ]; + + return pattern || + ( pattern = new RegExp( "(^|" + whitespace + + ")" + className + "(" + whitespace + "|$)" ) ) && classCache( + className, function( elem ) { + return pattern.test( + typeof elem.className === "string" && elem.className || + typeof elem.getAttribute !== "undefined" && + elem.getAttribute( "class" ) || + "" + ); + } ); + }, + + "ATTR": function( name, operator, check ) { + return function( elem ) { + var result = Sizzle.attr( elem, name ); + + if ( result == null ) { + return operator === "!="; + } + if ( !operator ) { + return true; + } + + result += ""; + + /* eslint-disable max-len */ + + return operator === "=" ? result === check : + operator === "!=" ? result !== check : + operator === "^=" ? check && result.indexOf( check ) === 0 : + operator === "*=" ? check && result.indexOf( check ) > -1 : + operator === "$=" ? check && result.slice( -check.length ) === check : + operator === "~=" ? ( " " + result.replace( rwhitespace, " " ) + " " ).indexOf( check ) > -1 : + operator === "|=" ? result === check || result.slice( 0, check.length + 1 ) === check + "-" : + false; + /* eslint-enable max-len */ + + }; + }, + + "CHILD": function( type, what, _argument, first, last ) { + var simple = type.slice( 0, 3 ) !== "nth", + forward = type.slice( -4 ) !== "last", + ofType = what === "of-type"; + + return first === 1 && last === 0 ? + + // Shortcut for :nth-*(n) + function( elem ) { + return !!elem.parentNode; + } : + + function( elem, _context, xml ) { + var cache, uniqueCache, outerCache, node, nodeIndex, start, + dir = simple !== forward ? "nextSibling" : "previousSibling", + parent = elem.parentNode, + name = ofType && elem.nodeName.toLowerCase(), + useCache = !xml && !ofType, + diff = false; + + if ( parent ) { + + // :(first|last|only)-(child|of-type) + if ( simple ) { + while ( dir ) { + node = elem; + while ( ( node = node[ dir ] ) ) { + if ( ofType ? + node.nodeName.toLowerCase() === name : + node.nodeType === 1 ) { + + return false; + } + } + + // Reverse direction for :only-* (if we haven't yet done so) + start = dir = type === "only" && !start && "nextSibling"; + } + return true; + } + + start = [ forward ? parent.firstChild : parent.lastChild ]; + + // non-xml :nth-child(...) stores cache data on `parent` + if ( forward && useCache ) { + + // Seek `elem` from a previously-cached index + + // ...in a gzip-friendly way + node = parent; + outerCache = node[ expando ] || ( node[ expando ] = {} ); + + // Support: IE <9 only + // Defend against cloned attroperties (jQuery gh-1709) + uniqueCache = outerCache[ node.uniqueID ] || + ( outerCache[ node.uniqueID ] = {} ); + + cache = uniqueCache[ type ] || []; + nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ]; + diff = nodeIndex && cache[ 2 ]; + node = nodeIndex && parent.childNodes[ nodeIndex ]; + + while ( ( node = ++nodeIndex && node && node[ dir ] || + + // Fallback to seeking `elem` from the start + ( diff = nodeIndex = 0 ) || start.pop() ) ) { + + // When found, cache indexes on `parent` and break + if ( node.nodeType === 1 && ++diff && node === elem ) { + uniqueCache[ type ] = [ dirruns, nodeIndex, diff ]; + break; + } + } + + } else { + + // Use previously-cached element index if available + if ( useCache ) { + + // ...in a gzip-friendly way + node = elem; + outerCache = node[ expando ] || ( node[ expando ] = {} ); + + // Support: IE <9 only + // Defend against cloned attroperties (jQuery gh-1709) + uniqueCache = outerCache[ node.uniqueID ] || + ( outerCache[ node.uniqueID ] = {} ); + + cache = uniqueCache[ type ] || []; + nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ]; + diff = nodeIndex; + } + + // xml :nth-child(...) + // or :nth-last-child(...) or :nth(-last)?-of-type(...) + if ( diff === false ) { + + // Use the same loop as above to seek `elem` from the start + while ( ( node = ++nodeIndex && node && node[ dir ] || + ( diff = nodeIndex = 0 ) || start.pop() ) ) { + + if ( ( ofType ? + node.nodeName.toLowerCase() === name : + node.nodeType === 1 ) && + ++diff ) { + + // Cache the index of each encountered element + if ( useCache ) { + outerCache = node[ expando ] || + ( node[ expando ] = {} ); + + // Support: IE <9 only + // Defend against cloned attroperties (jQuery gh-1709) + uniqueCache = outerCache[ node.uniqueID ] || + ( outerCache[ node.uniqueID ] = {} ); + + uniqueCache[ type ] = [ dirruns, diff ]; + } + + if ( node === elem ) { + break; + } + } + } + } + } + + // Incorporate the offset, then check against cycle size + diff -= last; + return diff === first || ( diff % first === 0 && diff / first >= 0 ); + } + }; + }, + + "PSEUDO": function( pseudo, argument ) { + + // pseudo-class names are case-insensitive + // http://www.w3.org/TR/selectors/#pseudo-classes + // Prioritize by case sensitivity in case custom pseudos are added with uppercase letters + // Remember that setFilters inherits from pseudos + var args, + fn = Expr.pseudos[ pseudo ] || Expr.setFilters[ pseudo.toLowerCase() ] || + Sizzle.error( "unsupported pseudo: " + pseudo ); + + // The user may use createPseudo to indicate that + // arguments are needed to create the filter function + // just as Sizzle does + if ( fn[ expando ] ) { + return fn( argument ); + } + + // But maintain support for old signatures + if ( fn.length > 1 ) { + args = [ pseudo, pseudo, "", argument ]; + return Expr.setFilters.hasOwnProperty( pseudo.toLowerCase() ) ? + markFunction( function( seed, matches ) { + var idx, + matched = fn( seed, argument ), + i = matched.length; + while ( i-- ) { + idx = indexOf( seed, matched[ i ] ); + seed[ idx ] = !( matches[ idx ] = matched[ i ] ); + } + } ) : + function( elem ) { + return fn( elem, 0, args ); + }; + } + + return fn; + } + }, + + pseudos: { + + // Potentially complex pseudos + "not": markFunction( function( selector ) { + + // Trim the selector passed to compile + // to avoid treating leading and trailing + // spaces as combinators + var input = [], + results = [], + matcher = compile( selector.replace( rtrim, "$1" ) ); + + return matcher[ expando ] ? + markFunction( function( seed, matches, _context, xml ) { + var elem, + unmatched = matcher( seed, null, xml, [] ), + i = seed.length; + + // Match elements unmatched by `matcher` + while ( i-- ) { + if ( ( elem = unmatched[ i ] ) ) { + seed[ i ] = !( matches[ i ] = elem ); + } + } + } ) : + function( elem, _context, xml ) { + input[ 0 ] = elem; + matcher( input, null, xml, results ); + + // Don't keep the element (issue #299) + input[ 0 ] = null; + return !results.pop(); + }; + } ), + + "has": markFunction( function( selector ) { + return function( elem ) { + return Sizzle( selector, elem ).length > 0; + }; + } ), + + "contains": markFunction( function( text ) { + text = text.replace( runescape, funescape ); + return function( elem ) { + return ( elem.textContent || getText( elem ) ).indexOf( text ) > -1; + }; + } ), + + // "Whether an element is represented by a :lang() selector + // is based solely on the element's language value + // being equal to the identifier C, + // or beginning with the identifier C immediately followed by "-". + // The matching of C against the element's language value is performed case-insensitively. + // The identifier C does not have to be a valid language name." + // http://www.w3.org/TR/selectors/#lang-pseudo + "lang": markFunction( function( lang ) { + + // lang value must be a valid identifier + if ( !ridentifier.test( lang || "" ) ) { + Sizzle.error( "unsupported lang: " + lang ); + } + lang = lang.replace( runescape, funescape ).toLowerCase(); + return function( elem ) { + var elemLang; + do { + if ( ( elemLang = documentIsHTML ? + elem.lang : + elem.getAttribute( "xml:lang" ) || elem.getAttribute( "lang" ) ) ) { + + elemLang = elemLang.toLowerCase(); + return elemLang === lang || elemLang.indexOf( lang + "-" ) === 0; + } + } while ( ( elem = elem.parentNode ) && elem.nodeType === 1 ); + return false; + }; + } ), + + // Miscellaneous + "target": function( elem ) { + var hash = window.location && window.location.hash; + return hash && hash.slice( 1 ) === elem.id; + }, + + "root": function( elem ) { + return elem === docElem; + }, + + "focus": function( elem ) { + return elem === document.activeElement && + ( !document.hasFocus || document.hasFocus() ) && + !!( elem.type || elem.href || ~elem.tabIndex ); + }, + + // Boolean properties + "enabled": createDisabledPseudo( false ), + "disabled": createDisabledPseudo( true ), + + "checked": function( elem ) { + + // In CSS3, :checked should return both checked and selected elements + // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked + var nodeName = elem.nodeName.toLowerCase(); + return ( nodeName === "input" && !!elem.checked ) || + ( nodeName === "option" && !!elem.selected ); + }, + + "selected": function( elem ) { + + // Accessing this property makes selected-by-default + // options in Safari work properly + if ( elem.parentNode ) { + // eslint-disable-next-line no-unused-expressions + elem.parentNode.selectedIndex; + } + + return elem.selected === true; + }, + + // Contents + "empty": function( elem ) { + + // http://www.w3.org/TR/selectors/#empty-pseudo + // :empty is negated by element (1) or content nodes (text: 3; cdata: 4; entity ref: 5), + // but not by others (comment: 8; processing instruction: 7; etc.) + // nodeType < 6 works because attributes (2) do not appear as children + for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { + if ( elem.nodeType < 6 ) { + return false; + } + } + return true; + }, + + "parent": function( elem ) { + return !Expr.pseudos[ "empty" ]( elem ); + }, + + // Element/input types + "header": function( elem ) { + return rheader.test( elem.nodeName ); + }, + + "input": function( elem ) { + return rinputs.test( elem.nodeName ); + }, + + "button": function( elem ) { + var name = elem.nodeName.toLowerCase(); + return name === "input" && elem.type === "button" || name === "button"; + }, + + "text": function( elem ) { + var attr; + return elem.nodeName.toLowerCase() === "input" && + elem.type === "text" && + + // Support: IE<8 + // New HTML5 attribute values (e.g., "search") appear with elem.type === "text" + ( ( attr = elem.getAttribute( "type" ) ) == null || + attr.toLowerCase() === "text" ); + }, + + // Position-in-collection + "first": createPositionalPseudo( function() { + return [ 0 ]; + } ), + + "last": createPositionalPseudo( function( _matchIndexes, length ) { + return [ length - 1 ]; + } ), + + "eq": createPositionalPseudo( function( _matchIndexes, length, argument ) { + return [ argument < 0 ? argument + length : argument ]; + } ), + + "even": createPositionalPseudo( function( matchIndexes, length ) { + var i = 0; + for ( ; i < length; i += 2 ) { + matchIndexes.push( i ); + } + return matchIndexes; + } ), + + "odd": createPositionalPseudo( function( matchIndexes, length ) { + var i = 1; + for ( ; i < length; i += 2 ) { + matchIndexes.push( i ); + } + return matchIndexes; + } ), + + "lt": createPositionalPseudo( function( matchIndexes, length, argument ) { + var i = argument < 0 ? + argument + length : + argument > length ? + length : + argument; + for ( ; --i >= 0; ) { + matchIndexes.push( i ); + } + return matchIndexes; + } ), + + "gt": createPositionalPseudo( function( matchIndexes, length, argument ) { + var i = argument < 0 ? argument + length : argument; + for ( ; ++i < length; ) { + matchIndexes.push( i ); + } + return matchIndexes; + } ) + } +}; + +Expr.pseudos[ "nth" ] = Expr.pseudos[ "eq" ]; + +// Add button/input type pseudos +for ( i in { radio: true, checkbox: true, file: true, password: true, image: true } ) { + Expr.pseudos[ i ] = createInputPseudo( i ); +} +for ( i in { submit: true, reset: true } ) { + Expr.pseudos[ i ] = createButtonPseudo( i ); +} + +// Easy API for creating new setFilters +function setFilters() {} +setFilters.prototype = Expr.filters = Expr.pseudos; +Expr.setFilters = new setFilters(); + +tokenize = Sizzle.tokenize = function( selector, parseOnly ) { + var matched, match, tokens, type, + soFar, groups, preFilters, + cached = tokenCache[ selector + " " ]; + + if ( cached ) { + return parseOnly ? 0 : cached.slice( 0 ); + } + + soFar = selector; + groups = []; + preFilters = Expr.preFilter; + + while ( soFar ) { + + // Comma and first run + if ( !matched || ( match = rcomma.exec( soFar ) ) ) { + if ( match ) { + + // Don't consume trailing commas as valid + soFar = soFar.slice( match[ 0 ].length ) || soFar; + } + groups.push( ( tokens = [] ) ); + } + + matched = false; + + // Combinators + if ( ( match = rcombinators.exec( soFar ) ) ) { + matched = match.shift(); + tokens.push( { + value: matched, + + // Cast descendant combinators to space + type: match[ 0 ].replace( rtrim, " " ) + } ); + soFar = soFar.slice( matched.length ); + } + + // Filters + for ( type in Expr.filter ) { + if ( ( match = matchExpr[ type ].exec( soFar ) ) && ( !preFilters[ type ] || + ( match = preFilters[ type ]( match ) ) ) ) { + matched = match.shift(); + tokens.push( { + value: matched, + type: type, + matches: match + } ); + soFar = soFar.slice( matched.length ); + } + } + + if ( !matched ) { + break; + } + } + + // Return the length of the invalid excess + // if we're just parsing + // Otherwise, throw an error or return tokens + return parseOnly ? + soFar.length : + soFar ? + Sizzle.error( selector ) : + + // Cache the tokens + tokenCache( selector, groups ).slice( 0 ); +}; + +function toSelector( tokens ) { + var i = 0, + len = tokens.length, + selector = ""; + for ( ; i < len; i++ ) { + selector += tokens[ i ].value; + } + return selector; +} + +function addCombinator( matcher, combinator, base ) { + var dir = combinator.dir, + skip = combinator.next, + key = skip || dir, + checkNonElements = base && key === "parentNode", + doneName = done++; + + return combinator.first ? + + // Check against closest ancestor/preceding element + function( elem, context, xml ) { + while ( ( elem = elem[ dir ] ) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + return matcher( elem, context, xml ); + } + } + return false; + } : + + // Check against all ancestor/preceding elements + function( elem, context, xml ) { + var oldCache, uniqueCache, outerCache, + newCache = [ dirruns, doneName ]; + + // We can't set arbitrary data on XML nodes, so they don't benefit from combinator caching + if ( xml ) { + while ( ( elem = elem[ dir ] ) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + if ( matcher( elem, context, xml ) ) { + return true; + } + } + } + } else { + while ( ( elem = elem[ dir ] ) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + outerCache = elem[ expando ] || ( elem[ expando ] = {} ); + + // Support: IE <9 only + // Defend against cloned attroperties (jQuery gh-1709) + uniqueCache = outerCache[ elem.uniqueID ] || + ( outerCache[ elem.uniqueID ] = {} ); + + if ( skip && skip === elem.nodeName.toLowerCase() ) { + elem = elem[ dir ] || elem; + } else if ( ( oldCache = uniqueCache[ key ] ) && + oldCache[ 0 ] === dirruns && oldCache[ 1 ] === doneName ) { + + // Assign to newCache so results back-propagate to previous elements + return ( newCache[ 2 ] = oldCache[ 2 ] ); + } else { + + // Reuse newcache so results back-propagate to previous elements + uniqueCache[ key ] = newCache; + + // A match means we're done; a fail means we have to keep checking + if ( ( newCache[ 2 ] = matcher( elem, context, xml ) ) ) { + return true; + } + } + } + } + } + return false; + }; +} + +function elementMatcher( matchers ) { + return matchers.length > 1 ? + function( elem, context, xml ) { + var i = matchers.length; + while ( i-- ) { + if ( !matchers[ i ]( elem, context, xml ) ) { + return false; + } + } + return true; + } : + matchers[ 0 ]; +} + +function multipleContexts( selector, contexts, results ) { + var i = 0, + len = contexts.length; + for ( ; i < len; i++ ) { + Sizzle( selector, contexts[ i ], results ); + } + return results; +} + +function condense( unmatched, map, filter, context, xml ) { + var elem, + newUnmatched = [], + i = 0, + len = unmatched.length, + mapped = map != null; + + for ( ; i < len; i++ ) { + if ( ( elem = unmatched[ i ] ) ) { + if ( !filter || filter( elem, context, xml ) ) { + newUnmatched.push( elem ); + if ( mapped ) { + map.push( i ); + } + } + } + } + + return newUnmatched; +} + +function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postSelector ) { + if ( postFilter && !postFilter[ expando ] ) { + postFilter = setMatcher( postFilter ); + } + if ( postFinder && !postFinder[ expando ] ) { + postFinder = setMatcher( postFinder, postSelector ); + } + return markFunction( function( seed, results, context, xml ) { + var temp, i, elem, + preMap = [], + postMap = [], + preexisting = results.length, + + // Get initial elements from seed or context + elems = seed || multipleContexts( + selector || "*", + context.nodeType ? [ context ] : context, + [] + ), + + // Prefilter to get matcher input, preserving a map for seed-results synchronization + matcherIn = preFilter && ( seed || !selector ) ? + condense( elems, preMap, preFilter, context, xml ) : + elems, + + matcherOut = matcher ? + + // If we have a postFinder, or filtered seed, or non-seed postFilter or preexisting results, + postFinder || ( seed ? preFilter : preexisting || postFilter ) ? + + // ...intermediate processing is necessary + [] : + + // ...otherwise use results directly + results : + matcherIn; + + // Find primary matches + if ( matcher ) { + matcher( matcherIn, matcherOut, context, xml ); + } + + // Apply postFilter + if ( postFilter ) { + temp = condense( matcherOut, postMap ); + postFilter( temp, [], context, xml ); + + // Un-match failing elements by moving them back to matcherIn + i = temp.length; + while ( i-- ) { + if ( ( elem = temp[ i ] ) ) { + matcherOut[ postMap[ i ] ] = !( matcherIn[ postMap[ i ] ] = elem ); + } + } + } + + if ( seed ) { + if ( postFinder || preFilter ) { + if ( postFinder ) { + + // Get the final matcherOut by condensing this intermediate into postFinder contexts + temp = []; + i = matcherOut.length; + while ( i-- ) { + if ( ( elem = matcherOut[ i ] ) ) { + + // Restore matcherIn since elem is not yet a final match + temp.push( ( matcherIn[ i ] = elem ) ); + } + } + postFinder( null, ( matcherOut = [] ), temp, xml ); + } + + // Move matched elements from seed to results to keep them synchronized + i = matcherOut.length; + while ( i-- ) { + if ( ( elem = matcherOut[ i ] ) && + ( temp = postFinder ? indexOf( seed, elem ) : preMap[ i ] ) > -1 ) { + + seed[ temp ] = !( results[ temp ] = elem ); + } + } + } + + // Add elements to results, through postFinder if defined + } else { + matcherOut = condense( + matcherOut === results ? + matcherOut.splice( preexisting, matcherOut.length ) : + matcherOut + ); + if ( postFinder ) { + postFinder( null, results, matcherOut, xml ); + } else { + push.apply( results, matcherOut ); + } + } + } ); +} + +function matcherFromTokens( tokens ) { + var checkContext, matcher, j, + len = tokens.length, + leadingRelative = Expr.relative[ tokens[ 0 ].type ], + implicitRelative = leadingRelative || Expr.relative[ " " ], + i = leadingRelative ? 1 : 0, + + // The foundational matcher ensures that elements are reachable from top-level context(s) + matchContext = addCombinator( function( elem ) { + return elem === checkContext; + }, implicitRelative, true ), + matchAnyContext = addCombinator( function( elem ) { + return indexOf( checkContext, elem ) > -1; + }, implicitRelative, true ), + matchers = [ function( elem, context, xml ) { + var ret = ( !leadingRelative && ( xml || context !== outermostContext ) ) || ( + ( checkContext = context ).nodeType ? + matchContext( elem, context, xml ) : + matchAnyContext( elem, context, xml ) ); + + // Avoid hanging onto element (issue #299) + checkContext = null; + return ret; + } ]; + + for ( ; i < len; i++ ) { + if ( ( matcher = Expr.relative[ tokens[ i ].type ] ) ) { + matchers = [ addCombinator( elementMatcher( matchers ), matcher ) ]; + } else { + matcher = Expr.filter[ tokens[ i ].type ].apply( null, tokens[ i ].matches ); + + // Return special upon seeing a positional matcher + if ( matcher[ expando ] ) { + + // Find the next relative operator (if any) for proper handling + j = ++i; + for ( ; j < len; j++ ) { + if ( Expr.relative[ tokens[ j ].type ] ) { + break; + } + } + return setMatcher( + i > 1 && elementMatcher( matchers ), + i > 1 && toSelector( + + // If the preceding token was a descendant combinator, insert an implicit any-element `*` + tokens + .slice( 0, i - 1 ) + .concat( { value: tokens[ i - 2 ].type === " " ? "*" : "" } ) + ).replace( rtrim, "$1" ), + matcher, + i < j && matcherFromTokens( tokens.slice( i, j ) ), + j < len && matcherFromTokens( ( tokens = tokens.slice( j ) ) ), + j < len && toSelector( tokens ) + ); + } + matchers.push( matcher ); + } + } + + return elementMatcher( matchers ); +} + +function matcherFromGroupMatchers( elementMatchers, setMatchers ) { + var bySet = setMatchers.length > 0, + byElement = elementMatchers.length > 0, + superMatcher = function( seed, context, xml, results, outermost ) { + var elem, j, matcher, + matchedCount = 0, + i = "0", + unmatched = seed && [], + setMatched = [], + contextBackup = outermostContext, + + // We must always have either seed elements or outermost context + elems = seed || byElement && Expr.find[ "TAG" ]( "*", outermost ), + + // Use integer dirruns iff this is the outermost matcher + dirrunsUnique = ( dirruns += contextBackup == null ? 1 : Math.random() || 0.1 ), + len = elems.length; + + if ( outermost ) { + + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + outermostContext = context == document || context || outermost; + } + + // Add elements passing elementMatchers directly to results + // Support: IE<9, Safari + // Tolerate NodeList properties (IE: "length"; Safari: ) matching elements by id + for ( ; i !== len && ( elem = elems[ i ] ) != null; i++ ) { + if ( byElement && elem ) { + j = 0; + + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( !context && elem.ownerDocument != document ) { + setDocument( elem ); + xml = !documentIsHTML; + } + while ( ( matcher = elementMatchers[ j++ ] ) ) { + if ( matcher( elem, context || document, xml ) ) { + results.push( elem ); + break; + } + } + if ( outermost ) { + dirruns = dirrunsUnique; + } + } + + // Track unmatched elements for set filters + if ( bySet ) { + + // They will have gone through all possible matchers + if ( ( elem = !matcher && elem ) ) { + matchedCount--; + } + + // Lengthen the array for every element, matched or not + if ( seed ) { + unmatched.push( elem ); + } + } + } + + // `i` is now the count of elements visited above, and adding it to `matchedCount` + // makes the latter nonnegative. + matchedCount += i; + + // Apply set filters to unmatched elements + // NOTE: This can be skipped if there are no unmatched elements (i.e., `matchedCount` + // equals `i`), unless we didn't visit _any_ elements in the above loop because we have + // no element matchers and no seed. + // Incrementing an initially-string "0" `i` allows `i` to remain a string only in that + // case, which will result in a "00" `matchedCount` that differs from `i` but is also + // numerically zero. + if ( bySet && i !== matchedCount ) { + j = 0; + while ( ( matcher = setMatchers[ j++ ] ) ) { + matcher( unmatched, setMatched, context, xml ); + } + + if ( seed ) { + + // Reintegrate element matches to eliminate the need for sorting + if ( matchedCount > 0 ) { + while ( i-- ) { + if ( !( unmatched[ i ] || setMatched[ i ] ) ) { + setMatched[ i ] = pop.call( results ); + } + } + } + + // Discard index placeholder values to get only actual matches + setMatched = condense( setMatched ); + } + + // Add matches to results + push.apply( results, setMatched ); + + // Seedless set matches succeeding multiple successful matchers stipulate sorting + if ( outermost && !seed && setMatched.length > 0 && + ( matchedCount + setMatchers.length ) > 1 ) { + + Sizzle.uniqueSort( results ); + } + } + + // Override manipulation of globals by nested matchers + if ( outermost ) { + dirruns = dirrunsUnique; + outermostContext = contextBackup; + } + + return unmatched; + }; + + return bySet ? + markFunction( superMatcher ) : + superMatcher; +} + +compile = Sizzle.compile = function( selector, match /* Internal Use Only */ ) { + var i, + setMatchers = [], + elementMatchers = [], + cached = compilerCache[ selector + " " ]; + + if ( !cached ) { + + // Generate a function of recursive functions that can be used to check each element + if ( !match ) { + match = tokenize( selector ); + } + i = match.length; + while ( i-- ) { + cached = matcherFromTokens( match[ i ] ); + if ( cached[ expando ] ) { + setMatchers.push( cached ); + } else { + elementMatchers.push( cached ); + } + } + + // Cache the compiled function + cached = compilerCache( + selector, + matcherFromGroupMatchers( elementMatchers, setMatchers ) + ); + + // Save selector and tokenization + cached.selector = selector; + } + return cached; +}; + +/** + * A low-level selection function that works with Sizzle's compiled + * selector functions + * @param {String|Function} selector A selector or a pre-compiled + * selector function built with Sizzle.compile + * @param {Element} context + * @param {Array} [results] + * @param {Array} [seed] A set of elements to match against + */ +select = Sizzle.select = function( selector, context, results, seed ) { + var i, tokens, token, type, find, + compiled = typeof selector === "function" && selector, + match = !seed && tokenize( ( selector = compiled.selector || selector ) ); + + results = results || []; + + // Try to minimize operations if there is only one selector in the list and no seed + // (the latter of which guarantees us context) + if ( match.length === 1 ) { + + // Reduce context if the leading compound selector is an ID + tokens = match[ 0 ] = match[ 0 ].slice( 0 ); + if ( tokens.length > 2 && ( token = tokens[ 0 ] ).type === "ID" && + context.nodeType === 9 && documentIsHTML && Expr.relative[ tokens[ 1 ].type ] ) { + + context = ( Expr.find[ "ID" ]( token.matches[ 0 ] + .replace( runescape, funescape ), context ) || [] )[ 0 ]; + if ( !context ) { + return results; + + // Precompiled matchers will still verify ancestry, so step up a level + } else if ( compiled ) { + context = context.parentNode; + } + + selector = selector.slice( tokens.shift().value.length ); + } + + // Fetch a seed set for right-to-left matching + i = matchExpr[ "needsContext" ].test( selector ) ? 0 : tokens.length; + while ( i-- ) { + token = tokens[ i ]; + + // Abort if we hit a combinator + if ( Expr.relative[ ( type = token.type ) ] ) { + break; + } + if ( ( find = Expr.find[ type ] ) ) { + + // Search, expanding context for leading sibling combinators + if ( ( seed = find( + token.matches[ 0 ].replace( runescape, funescape ), + rsibling.test( tokens[ 0 ].type ) && testContext( context.parentNode ) || + context + ) ) ) { + + // If seed is empty or no tokens remain, we can return early + tokens.splice( i, 1 ); + selector = seed.length && toSelector( tokens ); + if ( !selector ) { + push.apply( results, seed ); + return results; + } + + break; + } + } + } + } + + // Compile and execute a filtering function if one is not provided + // Provide `match` to avoid retokenization if we modified the selector above + ( compiled || compile( selector, match ) )( + seed, + context, + !documentIsHTML, + results, + !context || rsibling.test( selector ) && testContext( context.parentNode ) || context + ); + return results; +}; + +// One-time assignments + +// Sort stability +support.sortStable = expando.split( "" ).sort( sortOrder ).join( "" ) === expando; + +// Support: Chrome 14-35+ +// Always assume duplicates if they aren't passed to the comparison function +support.detectDuplicates = !!hasDuplicate; + +// Initialize against the default document +setDocument(); + +// Support: Webkit<537.32 - Safari 6.0.3/Chrome 25 (fixed in Chrome 27) +// Detached nodes confoundingly follow *each other* +support.sortDetached = assert( function( el ) { + + // Should return 1, but returns 4 (following) + return el.compareDocumentPosition( document.createElement( "fieldset" ) ) & 1; +} ); + +// Support: IE<8 +// Prevent attribute/property "interpolation" +// https://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx +if ( !assert( function( el ) { + el.innerHTML = ""; + return el.firstChild.getAttribute( "href" ) === "#"; +} ) ) { + addHandle( "type|href|height|width", function( elem, name, isXML ) { + if ( !isXML ) { + return elem.getAttribute( name, name.toLowerCase() === "type" ? 1 : 2 ); + } + } ); +} + +// Support: IE<9 +// Use defaultValue in place of getAttribute("value") +if ( !support.attributes || !assert( function( el ) { + el.innerHTML = ""; + el.firstChild.setAttribute( "value", "" ); + return el.firstChild.getAttribute( "value" ) === ""; +} ) ) { + addHandle( "value", function( elem, _name, isXML ) { + if ( !isXML && elem.nodeName.toLowerCase() === "input" ) { + return elem.defaultValue; + } + } ); +} + +// Support: IE<9 +// Use getAttributeNode to fetch booleans when getAttribute lies +if ( !assert( function( el ) { + return el.getAttribute( "disabled" ) == null; +} ) ) { + addHandle( booleans, function( elem, name, isXML ) { + var val; + if ( !isXML ) { + return elem[ name ] === true ? name.toLowerCase() : + ( val = elem.getAttributeNode( name ) ) && val.specified ? + val.value : + null; + } + } ); +} + +return Sizzle; + +} )( window ); + + + +jQuery.find = Sizzle; +jQuery.expr = Sizzle.selectors; + +// Deprecated +jQuery.expr[ ":" ] = jQuery.expr.pseudos; +jQuery.uniqueSort = jQuery.unique = Sizzle.uniqueSort; +jQuery.text = Sizzle.getText; +jQuery.isXMLDoc = Sizzle.isXML; +jQuery.contains = Sizzle.contains; +jQuery.escapeSelector = Sizzle.escape; + + + + +var dir = function( elem, dir, until ) { + var matched = [], + truncate = until !== undefined; + + while ( ( elem = elem[ dir ] ) && elem.nodeType !== 9 ) { + if ( elem.nodeType === 1 ) { + if ( truncate && jQuery( elem ).is( until ) ) { + break; + } + matched.push( elem ); + } + } + return matched; +}; + + +var siblings = function( n, elem ) { + var matched = []; + + for ( ; n; n = n.nextSibling ) { + if ( n.nodeType === 1 && n !== elem ) { + matched.push( n ); + } + } + + return matched; +}; + + +var rneedsContext = jQuery.expr.match.needsContext; + + + +function nodeName( elem, name ) { + + return elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase(); + +} +var rsingleTag = ( /^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i ); + + + +// Implement the identical functionality for filter and not +function winnow( elements, qualifier, not ) { + if ( isFunction( qualifier ) ) { + return jQuery.grep( elements, function( elem, i ) { + return !!qualifier.call( elem, i, elem ) !== not; + } ); + } + + // Single element + if ( qualifier.nodeType ) { + return jQuery.grep( elements, function( elem ) { + return ( elem === qualifier ) !== not; + } ); + } + + // Arraylike of elements (jQuery, arguments, Array) + if ( typeof qualifier !== "string" ) { + return jQuery.grep( elements, function( elem ) { + return ( indexOf.call( qualifier, elem ) > -1 ) !== not; + } ); + } + + // Filtered directly for both simple and complex selectors + return jQuery.filter( qualifier, elements, not ); +} + +jQuery.filter = function( expr, elems, not ) { + var elem = elems[ 0 ]; + + if ( not ) { + expr = ":not(" + expr + ")"; + } + + if ( elems.length === 1 && elem.nodeType === 1 ) { + return jQuery.find.matchesSelector( elem, expr ) ? [ elem ] : []; + } + + return jQuery.find.matches( expr, jQuery.grep( elems, function( elem ) { + return elem.nodeType === 1; + } ) ); +}; + +jQuery.fn.extend( { + find: function( selector ) { + var i, ret, + len = this.length, + self = this; + + if ( typeof selector !== "string" ) { + return this.pushStack( jQuery( selector ).filter( function() { + for ( i = 0; i < len; i++ ) { + if ( jQuery.contains( self[ i ], this ) ) { + return true; + } + } + } ) ); + } + + ret = this.pushStack( [] ); + + for ( i = 0; i < len; i++ ) { + jQuery.find( selector, self[ i ], ret ); + } + + return len > 1 ? jQuery.uniqueSort( ret ) : ret; + }, + filter: function( selector ) { + return this.pushStack( winnow( this, selector || [], false ) ); + }, + not: function( selector ) { + return this.pushStack( winnow( this, selector || [], true ) ); + }, + is: function( selector ) { + return !!winnow( + this, + + // If this is a positional/relative selector, check membership in the returned set + // so $("p:first").is("p:last") won't return true for a doc with two "p". + typeof selector === "string" && rneedsContext.test( selector ) ? + jQuery( selector ) : + selector || [], + false + ).length; + } +} ); + + +// Initialize a jQuery object + + +// A central reference to the root jQuery(document) +var rootjQuery, + + // A simple way to check for HTML strings + // Prioritize #id over to avoid XSS via location.hash (#9521) + // Strict HTML recognition (#11290: must start with <) + // Shortcut simple #id case for speed + rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/, + + init = jQuery.fn.init = function( selector, context, root ) { + var match, elem; + + // HANDLE: $(""), $(null), $(undefined), $(false) + if ( !selector ) { + return this; + } + + // Method init() accepts an alternate rootjQuery + // so migrate can support jQuery.sub (gh-2101) + root = root || rootjQuery; + + // Handle HTML strings + if ( typeof selector === "string" ) { + if ( selector[ 0 ] === "<" && + selector[ selector.length - 1 ] === ">" && + selector.length >= 3 ) { + + // Assume that strings that start and end with <> are HTML and skip the regex check + match = [ null, selector, null ]; + + } else { + match = rquickExpr.exec( selector ); + } + + // Match html or make sure no context is specified for #id + if ( match && ( match[ 1 ] || !context ) ) { + + // HANDLE: $(html) -> $(array) + if ( match[ 1 ] ) { + context = context instanceof jQuery ? context[ 0 ] : context; + + // Option to run scripts is true for back-compat + // Intentionally let the error be thrown if parseHTML is not present + jQuery.merge( this, jQuery.parseHTML( + match[ 1 ], + context && context.nodeType ? context.ownerDocument || context : document, + true + ) ); + + // HANDLE: $(html, props) + if ( rsingleTag.test( match[ 1 ] ) && jQuery.isPlainObject( context ) ) { + for ( match in context ) { + + // Properties of context are called as methods if possible + if ( isFunction( this[ match ] ) ) { + this[ match ]( context[ match ] ); + + // ...and otherwise set as attributes + } else { + this.attr( match, context[ match ] ); + } + } + } + + return this; + + // HANDLE: $(#id) + } else { + elem = document.getElementById( match[ 2 ] ); + + if ( elem ) { + + // Inject the element directly into the jQuery object + this[ 0 ] = elem; + this.length = 1; + } + return this; + } + + // HANDLE: $(expr, $(...)) + } else if ( !context || context.jquery ) { + return ( context || root ).find( selector ); + + // HANDLE: $(expr, context) + // (which is just equivalent to: $(context).find(expr) + } else { + return this.constructor( context ).find( selector ); + } + + // HANDLE: $(DOMElement) + } else if ( selector.nodeType ) { + this[ 0 ] = selector; + this.length = 1; + return this; + + // HANDLE: $(function) + // Shortcut for document ready + } else if ( isFunction( selector ) ) { + return root.ready !== undefined ? + root.ready( selector ) : + + // Execute immediately if ready is not present + selector( jQuery ); + } + + return jQuery.makeArray( selector, this ); + }; + +// Give the init function the jQuery prototype for later instantiation +init.prototype = jQuery.fn; + +// Initialize central reference +rootjQuery = jQuery( document ); + + +var rparentsprev = /^(?:parents|prev(?:Until|All))/, + + // Methods guaranteed to produce a unique set when starting from a unique set + guaranteedUnique = { + children: true, + contents: true, + next: true, + prev: true + }; + +jQuery.fn.extend( { + has: function( target ) { + var targets = jQuery( target, this ), + l = targets.length; + + return this.filter( function() { + var i = 0; + for ( ; i < l; i++ ) { + if ( jQuery.contains( this, targets[ i ] ) ) { + return true; + } + } + } ); + }, + + closest: function( selectors, context ) { + var cur, + i = 0, + l = this.length, + matched = [], + targets = typeof selectors !== "string" && jQuery( selectors ); + + // Positional selectors never match, since there's no _selection_ context + if ( !rneedsContext.test( selectors ) ) { + for ( ; i < l; i++ ) { + for ( cur = this[ i ]; cur && cur !== context; cur = cur.parentNode ) { + + // Always skip document fragments + if ( cur.nodeType < 11 && ( targets ? + targets.index( cur ) > -1 : + + // Don't pass non-elements to Sizzle + cur.nodeType === 1 && + jQuery.find.matchesSelector( cur, selectors ) ) ) { + + matched.push( cur ); + break; + } + } + } + } + + return this.pushStack( matched.length > 1 ? jQuery.uniqueSort( matched ) : matched ); + }, + + // Determine the position of an element within the set + index: function( elem ) { + + // No argument, return index in parent + if ( !elem ) { + return ( this[ 0 ] && this[ 0 ].parentNode ) ? this.first().prevAll().length : -1; + } + + // Index in selector + if ( typeof elem === "string" ) { + return indexOf.call( jQuery( elem ), this[ 0 ] ); + } + + // Locate the position of the desired element + return indexOf.call( this, + + // If it receives a jQuery object, the first element is used + elem.jquery ? elem[ 0 ] : elem + ); + }, + + add: function( selector, context ) { + return this.pushStack( + jQuery.uniqueSort( + jQuery.merge( this.get(), jQuery( selector, context ) ) + ) + ); + }, + + addBack: function( selector ) { + return this.add( selector == null ? + this.prevObject : this.prevObject.filter( selector ) + ); + } +} ); + +function sibling( cur, dir ) { + while ( ( cur = cur[ dir ] ) && cur.nodeType !== 1 ) {} + return cur; +} + +jQuery.each( { + parent: function( elem ) { + var parent = elem.parentNode; + return parent && parent.nodeType !== 11 ? parent : null; + }, + parents: function( elem ) { + return dir( elem, "parentNode" ); + }, + parentsUntil: function( elem, _i, until ) { + return dir( elem, "parentNode", until ); + }, + next: function( elem ) { + return sibling( elem, "nextSibling" ); + }, + prev: function( elem ) { + return sibling( elem, "previousSibling" ); + }, + nextAll: function( elem ) { + return dir( elem, "nextSibling" ); + }, + prevAll: function( elem ) { + return dir( elem, "previousSibling" ); + }, + nextUntil: function( elem, _i, until ) { + return dir( elem, "nextSibling", until ); + }, + prevUntil: function( elem, _i, until ) { + return dir( elem, "previousSibling", until ); + }, + siblings: function( elem ) { + return siblings( ( elem.parentNode || {} ).firstChild, elem ); + }, + children: function( elem ) { + return siblings( elem.firstChild ); + }, + contents: function( elem ) { + if ( elem.contentDocument != null && + + // Support: IE 11+ + // elements with no `data` attribute has an object + // `contentDocument` with a `null` prototype. + getProto( elem.contentDocument ) ) { + + return elem.contentDocument; + } + + // Support: IE 9 - 11 only, iOS 7 only, Android Browser <=4.3 only + // Treat the template element as a regular one in browsers that + // don't support it. + if ( nodeName( elem, "template" ) ) { + elem = elem.content || elem; + } + + return jQuery.merge( [], elem.childNodes ); + } +}, function( name, fn ) { + jQuery.fn[ name ] = function( until, selector ) { + var matched = jQuery.map( this, fn, until ); + + if ( name.slice( -5 ) !== "Until" ) { + selector = until; + } + + if ( selector && typeof selector === "string" ) { + matched = jQuery.filter( selector, matched ); + } + + if ( this.length > 1 ) { + + // Remove duplicates + if ( !guaranteedUnique[ name ] ) { + jQuery.uniqueSort( matched ); + } + + // Reverse order for parents* and prev-derivatives + if ( rparentsprev.test( name ) ) { + matched.reverse(); + } + } + + return this.pushStack( matched ); + }; +} ); +var rnothtmlwhite = ( /[^\x20\t\r\n\f]+/g ); + + + +// Convert String-formatted options into Object-formatted ones +function createOptions( options ) { + var object = {}; + jQuery.each( options.match( rnothtmlwhite ) || [], function( _, flag ) { + object[ flag ] = true; + } ); + return object; +} + +/* + * Create a callback list using the following parameters: + * + * options: an optional list of space-separated options that will change how + * the callback list behaves or a more traditional option object + * + * By default a callback list will act like an event callback list and can be + * "fired" multiple times. + * + * Possible options: + * + * once: will ensure the callback list can only be fired once (like a Deferred) + * + * memory: will keep track of previous values and will call any callback added + * after the list has been fired right away with the latest "memorized" + * values (like a Deferred) + * + * unique: will ensure a callback can only be added once (no duplicate in the list) + * + * stopOnFalse: interrupt callings when a callback returns false + * + */ +jQuery.Callbacks = function( options ) { + + // Convert options from String-formatted to Object-formatted if needed + // (we check in cache first) + options = typeof options === "string" ? + createOptions( options ) : + jQuery.extend( {}, options ); + + var // Flag to know if list is currently firing + firing, + + // Last fire value for non-forgettable lists + memory, + + // Flag to know if list was already fired + fired, + + // Flag to prevent firing + locked, + + // Actual callback list + list = [], + + // Queue of execution data for repeatable lists + queue = [], + + // Index of currently firing callback (modified by add/remove as needed) + firingIndex = -1, + + // Fire callbacks + fire = function() { + + // Enforce single-firing + locked = locked || options.once; + + // Execute callbacks for all pending executions, + // respecting firingIndex overrides and runtime changes + fired = firing = true; + for ( ; queue.length; firingIndex = -1 ) { + memory = queue.shift(); + while ( ++firingIndex < list.length ) { + + // Run callback and check for early termination + if ( list[ firingIndex ].apply( memory[ 0 ], memory[ 1 ] ) === false && + options.stopOnFalse ) { + + // Jump to end and forget the data so .add doesn't re-fire + firingIndex = list.length; + memory = false; + } + } + } + + // Forget the data if we're done with it + if ( !options.memory ) { + memory = false; + } + + firing = false; + + // Clean up if we're done firing for good + if ( locked ) { + + // Keep an empty list if we have data for future add calls + if ( memory ) { + list = []; + + // Otherwise, this object is spent + } else { + list = ""; + } + } + }, + + // Actual Callbacks object + self = { + + // Add a callback or a collection of callbacks to the list + add: function() { + if ( list ) { + + // If we have memory from a past run, we should fire after adding + if ( memory && !firing ) { + firingIndex = list.length - 1; + queue.push( memory ); + } + + ( function add( args ) { + jQuery.each( args, function( _, arg ) { + if ( isFunction( arg ) ) { + if ( !options.unique || !self.has( arg ) ) { + list.push( arg ); + } + } else if ( arg && arg.length && toType( arg ) !== "string" ) { + + // Inspect recursively + add( arg ); + } + } ); + } )( arguments ); + + if ( memory && !firing ) { + fire(); + } + } + return this; + }, + + // Remove a callback from the list + remove: function() { + jQuery.each( arguments, function( _, arg ) { + var index; + while ( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) { + list.splice( index, 1 ); + + // Handle firing indexes + if ( index <= firingIndex ) { + firingIndex--; + } + } + } ); + return this; + }, + + // Check if a given callback is in the list. + // If no argument is given, return whether or not list has callbacks attached. + has: function( fn ) { + return fn ? + jQuery.inArray( fn, list ) > -1 : + list.length > 0; + }, + + // Remove all callbacks from the list + empty: function() { + if ( list ) { + list = []; + } + return this; + }, + + // Disable .fire and .add + // Abort any current/pending executions + // Clear all callbacks and values + disable: function() { + locked = queue = []; + list = memory = ""; + return this; + }, + disabled: function() { + return !list; + }, + + // Disable .fire + // Also disable .add unless we have memory (since it would have no effect) + // Abort any pending executions + lock: function() { + locked = queue = []; + if ( !memory && !firing ) { + list = memory = ""; + } + return this; + }, + locked: function() { + return !!locked; + }, + + // Call all callbacks with the given context and arguments + fireWith: function( context, args ) { + if ( !locked ) { + args = args || []; + args = [ context, args.slice ? args.slice() : args ]; + queue.push( args ); + if ( !firing ) { + fire(); + } + } + return this; + }, + + // Call all the callbacks with the given arguments + fire: function() { + self.fireWith( this, arguments ); + return this; + }, + + // To know if the callbacks have already been called at least once + fired: function() { + return !!fired; + } + }; + + return self; +}; + + +function Identity( v ) { + return v; +} +function Thrower( ex ) { + throw ex; +} + +function adoptValue( value, resolve, reject, noValue ) { + var method; + + try { + + // Check for promise aspect first to privilege synchronous behavior + if ( value && isFunction( ( method = value.promise ) ) ) { + method.call( value ).done( resolve ).fail( reject ); + + // Other thenables + } else if ( value && isFunction( ( method = value.then ) ) ) { + method.call( value, resolve, reject ); + + // Other non-thenables + } else { + + // Control `resolve` arguments by letting Array#slice cast boolean `noValue` to integer: + // * false: [ value ].slice( 0 ) => resolve( value ) + // * true: [ value ].slice( 1 ) => resolve() + resolve.apply( undefined, [ value ].slice( noValue ) ); + } + + // For Promises/A+, convert exceptions into rejections + // Since jQuery.when doesn't unwrap thenables, we can skip the extra checks appearing in + // Deferred#then to conditionally suppress rejection. + } catch ( value ) { + + // Support: Android 4.0 only + // Strict mode functions invoked without .call/.apply get global-object context + reject.apply( undefined, [ value ] ); + } +} + +jQuery.extend( { + + Deferred: function( func ) { + var tuples = [ + + // action, add listener, callbacks, + // ... .then handlers, argument index, [final state] + [ "notify", "progress", jQuery.Callbacks( "memory" ), + jQuery.Callbacks( "memory" ), 2 ], + [ "resolve", "done", jQuery.Callbacks( "once memory" ), + jQuery.Callbacks( "once memory" ), 0, "resolved" ], + [ "reject", "fail", jQuery.Callbacks( "once memory" ), + jQuery.Callbacks( "once memory" ), 1, "rejected" ] + ], + state = "pending", + promise = { + state: function() { + return state; + }, + always: function() { + deferred.done( arguments ).fail( arguments ); + return this; + }, + "catch": function( fn ) { + return promise.then( null, fn ); + }, + + // Keep pipe for back-compat + pipe: function( /* fnDone, fnFail, fnProgress */ ) { + var fns = arguments; + + return jQuery.Deferred( function( newDefer ) { + jQuery.each( tuples, function( _i, tuple ) { + + // Map tuples (progress, done, fail) to arguments (done, fail, progress) + var fn = isFunction( fns[ tuple[ 4 ] ] ) && fns[ tuple[ 4 ] ]; + + // deferred.progress(function() { bind to newDefer or newDefer.notify }) + // deferred.done(function() { bind to newDefer or newDefer.resolve }) + // deferred.fail(function() { bind to newDefer or newDefer.reject }) + deferred[ tuple[ 1 ] ]( function() { + var returned = fn && fn.apply( this, arguments ); + if ( returned && isFunction( returned.promise ) ) { + returned.promise() + .progress( newDefer.notify ) + .done( newDefer.resolve ) + .fail( newDefer.reject ); + } else { + newDefer[ tuple[ 0 ] + "With" ]( + this, + fn ? [ returned ] : arguments + ); + } + } ); + } ); + fns = null; + } ).promise(); + }, + then: function( onFulfilled, onRejected, onProgress ) { + var maxDepth = 0; + function resolve( depth, deferred, handler, special ) { + return function() { + var that = this, + args = arguments, + mightThrow = function() { + var returned, then; + + // Support: Promises/A+ section 2.3.3.3.3 + // https://promisesaplus.com/#point-59 + // Ignore double-resolution attempts + if ( depth < maxDepth ) { + return; + } + + returned = handler.apply( that, args ); + + // Support: Promises/A+ section 2.3.1 + // https://promisesaplus.com/#point-48 + if ( returned === deferred.promise() ) { + throw new TypeError( "Thenable self-resolution" ); + } + + // Support: Promises/A+ sections 2.3.3.1, 3.5 + // https://promisesaplus.com/#point-54 + // https://promisesaplus.com/#point-75 + // Retrieve `then` only once + then = returned && + + // Support: Promises/A+ section 2.3.4 + // https://promisesaplus.com/#point-64 + // Only check objects and functions for thenability + ( typeof returned === "object" || + typeof returned === "function" ) && + returned.then; + + // Handle a returned thenable + if ( isFunction( then ) ) { + + // Special processors (notify) just wait for resolution + if ( special ) { + then.call( + returned, + resolve( maxDepth, deferred, Identity, special ), + resolve( maxDepth, deferred, Thrower, special ) + ); + + // Normal processors (resolve) also hook into progress + } else { + + // ...and disregard older resolution values + maxDepth++; + + then.call( + returned, + resolve( maxDepth, deferred, Identity, special ), + resolve( maxDepth, deferred, Thrower, special ), + resolve( maxDepth, deferred, Identity, + deferred.notifyWith ) + ); + } + + // Handle all other returned values + } else { + + // Only substitute handlers pass on context + // and multiple values (non-spec behavior) + if ( handler !== Identity ) { + that = undefined; + args = [ returned ]; + } + + // Process the value(s) + // Default process is resolve + ( special || deferred.resolveWith )( that, args ); + } + }, + + // Only normal processors (resolve) catch and reject exceptions + process = special ? + mightThrow : + function() { + try { + mightThrow(); + } catch ( e ) { + + if ( jQuery.Deferred.exceptionHook ) { + jQuery.Deferred.exceptionHook( e, + process.stackTrace ); + } + + // Support: Promises/A+ section 2.3.3.3.4.1 + // https://promisesaplus.com/#point-61 + // Ignore post-resolution exceptions + if ( depth + 1 >= maxDepth ) { + + // Only substitute handlers pass on context + // and multiple values (non-spec behavior) + if ( handler !== Thrower ) { + that = undefined; + args = [ e ]; + } + + deferred.rejectWith( that, args ); + } + } + }; + + // Support: Promises/A+ section 2.3.3.3.1 + // https://promisesaplus.com/#point-57 + // Re-resolve promises immediately to dodge false rejection from + // subsequent errors + if ( depth ) { + process(); + } else { + + // Call an optional hook to record the stack, in case of exception + // since it's otherwise lost when execution goes async + if ( jQuery.Deferred.getStackHook ) { + process.stackTrace = jQuery.Deferred.getStackHook(); + } + window.setTimeout( process ); + } + }; + } + + return jQuery.Deferred( function( newDefer ) { + + // progress_handlers.add( ... ) + tuples[ 0 ][ 3 ].add( + resolve( + 0, + newDefer, + isFunction( onProgress ) ? + onProgress : + Identity, + newDefer.notifyWith + ) + ); + + // fulfilled_handlers.add( ... ) + tuples[ 1 ][ 3 ].add( + resolve( + 0, + newDefer, + isFunction( onFulfilled ) ? + onFulfilled : + Identity + ) + ); + + // rejected_handlers.add( ... ) + tuples[ 2 ][ 3 ].add( + resolve( + 0, + newDefer, + isFunction( onRejected ) ? + onRejected : + Thrower + ) + ); + } ).promise(); + }, + + // Get a promise for this deferred + // If obj is provided, the promise aspect is added to the object + promise: function( obj ) { + return obj != null ? jQuery.extend( obj, promise ) : promise; + } + }, + deferred = {}; + + // Add list-specific methods + jQuery.each( tuples, function( i, tuple ) { + var list = tuple[ 2 ], + stateString = tuple[ 5 ]; + + // promise.progress = list.add + // promise.done = list.add + // promise.fail = list.add + promise[ tuple[ 1 ] ] = list.add; + + // Handle state + if ( stateString ) { + list.add( + function() { + + // state = "resolved" (i.e., fulfilled) + // state = "rejected" + state = stateString; + }, + + // rejected_callbacks.disable + // fulfilled_callbacks.disable + tuples[ 3 - i ][ 2 ].disable, + + // rejected_handlers.disable + // fulfilled_handlers.disable + tuples[ 3 - i ][ 3 ].disable, + + // progress_callbacks.lock + tuples[ 0 ][ 2 ].lock, + + // progress_handlers.lock + tuples[ 0 ][ 3 ].lock + ); + } + + // progress_handlers.fire + // fulfilled_handlers.fire + // rejected_handlers.fire + list.add( tuple[ 3 ].fire ); + + // deferred.notify = function() { deferred.notifyWith(...) } + // deferred.resolve = function() { deferred.resolveWith(...) } + // deferred.reject = function() { deferred.rejectWith(...) } + deferred[ tuple[ 0 ] ] = function() { + deferred[ tuple[ 0 ] + "With" ]( this === deferred ? undefined : this, arguments ); + return this; + }; + + // deferred.notifyWith = list.fireWith + // deferred.resolveWith = list.fireWith + // deferred.rejectWith = list.fireWith + deferred[ tuple[ 0 ] + "With" ] = list.fireWith; + } ); + + // Make the deferred a promise + promise.promise( deferred ); + + // Call given func if any + if ( func ) { + func.call( deferred, deferred ); + } + + // All done! + return deferred; + }, + + // Deferred helper + when: function( singleValue ) { + var + + // count of uncompleted subordinates + remaining = arguments.length, + + // count of unprocessed arguments + i = remaining, + + // subordinate fulfillment data + resolveContexts = Array( i ), + resolveValues = slice.call( arguments ), + + // the primary Deferred + primary = jQuery.Deferred(), + + // subordinate callback factory + updateFunc = function( i ) { + return function( value ) { + resolveContexts[ i ] = this; + resolveValues[ i ] = arguments.length > 1 ? slice.call( arguments ) : value; + if ( !( --remaining ) ) { + primary.resolveWith( resolveContexts, resolveValues ); + } + }; + }; + + // Single- and empty arguments are adopted like Promise.resolve + if ( remaining <= 1 ) { + adoptValue( singleValue, primary.done( updateFunc( i ) ).resolve, primary.reject, + !remaining ); + + // Use .then() to unwrap secondary thenables (cf. gh-3000) + if ( primary.state() === "pending" || + isFunction( resolveValues[ i ] && resolveValues[ i ].then ) ) { + + return primary.then(); + } + } + + // Multiple arguments are aggregated like Promise.all array elements + while ( i-- ) { + adoptValue( resolveValues[ i ], updateFunc( i ), primary.reject ); + } + + return primary.promise(); + } +} ); + + +// These usually indicate a programmer mistake during development, +// warn about them ASAP rather than swallowing them by default. +var rerrorNames = /^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/; + +jQuery.Deferred.exceptionHook = function( error, stack ) { + + // Support: IE 8 - 9 only + // Console exists when dev tools are open, which can happen at any time + if ( window.console && window.console.warn && error && rerrorNames.test( error.name ) ) { + window.console.warn( "jQuery.Deferred exception: " + error.message, error.stack, stack ); + } +}; + + + + +jQuery.readyException = function( error ) { + window.setTimeout( function() { + throw error; + } ); +}; + + + + +// The deferred used on DOM ready +var readyList = jQuery.Deferred(); + +jQuery.fn.ready = function( fn ) { + + readyList + .then( fn ) + + // Wrap jQuery.readyException in a function so that the lookup + // happens at the time of error handling instead of callback + // registration. + .catch( function( error ) { + jQuery.readyException( error ); + } ); + + return this; +}; + +jQuery.extend( { + + // Is the DOM ready to be used? Set to true once it occurs. + isReady: false, + + // A counter to track how many items to wait for before + // the ready event fires. See #6781 + readyWait: 1, + + // Handle when the DOM is ready + ready: function( wait ) { + + // Abort if there are pending holds or we're already ready + if ( wait === true ? --jQuery.readyWait : jQuery.isReady ) { + return; + } + + // Remember that the DOM is ready + jQuery.isReady = true; + + // If a normal DOM Ready event fired, decrement, and wait if need be + if ( wait !== true && --jQuery.readyWait > 0 ) { + return; + } + + // If there are functions bound, to execute + readyList.resolveWith( document, [ jQuery ] ); + } +} ); + +jQuery.ready.then = readyList.then; + +// The ready event handler and self cleanup method +function completed() { + document.removeEventListener( "DOMContentLoaded", completed ); + window.removeEventListener( "load", completed ); + jQuery.ready(); +} + +// Catch cases where $(document).ready() is called +// after the browser event has already occurred. +// Support: IE <=9 - 10 only +// Older IE sometimes signals "interactive" too soon +if ( document.readyState === "complete" || + ( document.readyState !== "loading" && !document.documentElement.doScroll ) ) { + + // Handle it asynchronously to allow scripts the opportunity to delay ready + window.setTimeout( jQuery.ready ); + +} else { + + // Use the handy event callback + document.addEventListener( "DOMContentLoaded", completed ); + + // A fallback to window.onload, that will always work + window.addEventListener( "load", completed ); +} + + + + +// Multifunctional method to get and set values of a collection +// The value/s can optionally be executed if it's a function +var access = function( elems, fn, key, value, chainable, emptyGet, raw ) { + var i = 0, + len = elems.length, + bulk = key == null; + + // Sets many values + if ( toType( key ) === "object" ) { + chainable = true; + for ( i in key ) { + access( elems, fn, i, key[ i ], true, emptyGet, raw ); + } + + // Sets one value + } else if ( value !== undefined ) { + chainable = true; + + if ( !isFunction( value ) ) { + raw = true; + } + + if ( bulk ) { + + // Bulk operations run against the entire set + if ( raw ) { + fn.call( elems, value ); + fn = null; + + // ...except when executing function values + } else { + bulk = fn; + fn = function( elem, _key, value ) { + return bulk.call( jQuery( elem ), value ); + }; + } + } + + if ( fn ) { + for ( ; i < len; i++ ) { + fn( + elems[ i ], key, raw ? + value : + value.call( elems[ i ], i, fn( elems[ i ], key ) ) + ); + } + } + } + + if ( chainable ) { + return elems; + } + + // Gets + if ( bulk ) { + return fn.call( elems ); + } + + return len ? fn( elems[ 0 ], key ) : emptyGet; +}; + + +// Matches dashed string for camelizing +var rmsPrefix = /^-ms-/, + rdashAlpha = /-([a-z])/g; + +// Used by camelCase as callback to replace() +function fcamelCase( _all, letter ) { + return letter.toUpperCase(); +} + +// Convert dashed to camelCase; used by the css and data modules +// Support: IE <=9 - 11, Edge 12 - 15 +// Microsoft forgot to hump their vendor prefix (#9572) +function camelCase( string ) { + return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase ); +} +var acceptData = function( owner ) { + + // Accepts only: + // - Node + // - Node.ELEMENT_NODE + // - Node.DOCUMENT_NODE + // - Object + // - Any + return owner.nodeType === 1 || owner.nodeType === 9 || !( +owner.nodeType ); +}; + + + + +function Data() { + this.expando = jQuery.expando + Data.uid++; +} + +Data.uid = 1; + +Data.prototype = { + + cache: function( owner ) { + + // Check if the owner object already has a cache + var value = owner[ this.expando ]; + + // If not, create one + if ( !value ) { + value = {}; + + // We can accept data for non-element nodes in modern browsers, + // but we should not, see #8335. + // Always return an empty object. + if ( acceptData( owner ) ) { + + // If it is a node unlikely to be stringify-ed or looped over + // use plain assignment + if ( owner.nodeType ) { + owner[ this.expando ] = value; + + // Otherwise secure it in a non-enumerable property + // configurable must be true to allow the property to be + // deleted when data is removed + } else { + Object.defineProperty( owner, this.expando, { + value: value, + configurable: true + } ); + } + } + } + + return value; + }, + set: function( owner, data, value ) { + var prop, + cache = this.cache( owner ); + + // Handle: [ owner, key, value ] args + // Always use camelCase key (gh-2257) + if ( typeof data === "string" ) { + cache[ camelCase( data ) ] = value; + + // Handle: [ owner, { properties } ] args + } else { + + // Copy the properties one-by-one to the cache object + for ( prop in data ) { + cache[ camelCase( prop ) ] = data[ prop ]; + } + } + return cache; + }, + get: function( owner, key ) { + return key === undefined ? + this.cache( owner ) : + + // Always use camelCase key (gh-2257) + owner[ this.expando ] && owner[ this.expando ][ camelCase( key ) ]; + }, + access: function( owner, key, value ) { + + // In cases where either: + // + // 1. No key was specified + // 2. A string key was specified, but no value provided + // + // Take the "read" path and allow the get method to determine + // which value to return, respectively either: + // + // 1. The entire cache object + // 2. The data stored at the key + // + if ( key === undefined || + ( ( key && typeof key === "string" ) && value === undefined ) ) { + + return this.get( owner, key ); + } + + // When the key is not a string, or both a key and value + // are specified, set or extend (existing objects) with either: + // + // 1. An object of properties + // 2. A key and value + // + this.set( owner, key, value ); + + // Since the "set" path can have two possible entry points + // return the expected data based on which path was taken[*] + return value !== undefined ? value : key; + }, + remove: function( owner, key ) { + var i, + cache = owner[ this.expando ]; + + if ( cache === undefined ) { + return; + } + + if ( key !== undefined ) { + + // Support array or space separated string of keys + if ( Array.isArray( key ) ) { + + // If key is an array of keys... + // We always set camelCase keys, so remove that. + key = key.map( camelCase ); + } else { + key = camelCase( key ); + + // If a key with the spaces exists, use it. + // Otherwise, create an array by matching non-whitespace + key = key in cache ? + [ key ] : + ( key.match( rnothtmlwhite ) || [] ); + } + + i = key.length; + + while ( i-- ) { + delete cache[ key[ i ] ]; + } + } + + // Remove the expando if there's no more data + if ( key === undefined || jQuery.isEmptyObject( cache ) ) { + + // Support: Chrome <=35 - 45 + // Webkit & Blink performance suffers when deleting properties + // from DOM nodes, so set to undefined instead + // https://bugs.chromium.org/p/chromium/issues/detail?id=378607 (bug restricted) + if ( owner.nodeType ) { + owner[ this.expando ] = undefined; + } else { + delete owner[ this.expando ]; + } + } + }, + hasData: function( owner ) { + var cache = owner[ this.expando ]; + return cache !== undefined && !jQuery.isEmptyObject( cache ); + } +}; +var dataPriv = new Data(); + +var dataUser = new Data(); + + + +// Implementation Summary +// +// 1. Enforce API surface and semantic compatibility with 1.9.x branch +// 2. Improve the module's maintainability by reducing the storage +// paths to a single mechanism. +// 3. Use the same single mechanism to support "private" and "user" data. +// 4. _Never_ expose "private" data to user code (TODO: Drop _data, _removeData) +// 5. Avoid exposing implementation details on user objects (eg. expando properties) +// 6. Provide a clear path for implementation upgrade to WeakMap in 2014 + +var rbrace = /^(?:\{[\w\W]*\}|\[[\w\W]*\])$/, + rmultiDash = /[A-Z]/g; + +function getData( data ) { + if ( data === "true" ) { + return true; + } + + if ( data === "false" ) { + return false; + } + + if ( data === "null" ) { + return null; + } + + // Only convert to a number if it doesn't change the string + if ( data === +data + "" ) { + return +data; + } + + if ( rbrace.test( data ) ) { + return JSON.parse( data ); + } + + return data; +} + +function dataAttr( elem, key, data ) { + var name; + + // If nothing was found internally, try to fetch any + // data from the HTML5 data-* attribute + if ( data === undefined && elem.nodeType === 1 ) { + name = "data-" + key.replace( rmultiDash, "-$&" ).toLowerCase(); + data = elem.getAttribute( name ); + + if ( typeof data === "string" ) { + try { + data = getData( data ); + } catch ( e ) {} + + // Make sure we set the data so it isn't changed later + dataUser.set( elem, key, data ); + } else { + data = undefined; + } + } + return data; +} + +jQuery.extend( { + hasData: function( elem ) { + return dataUser.hasData( elem ) || dataPriv.hasData( elem ); + }, + + data: function( elem, name, data ) { + return dataUser.access( elem, name, data ); + }, + + removeData: function( elem, name ) { + dataUser.remove( elem, name ); + }, + + // TODO: Now that all calls to _data and _removeData have been replaced + // with direct calls to dataPriv methods, these can be deprecated. + _data: function( elem, name, data ) { + return dataPriv.access( elem, name, data ); + }, + + _removeData: function( elem, name ) { + dataPriv.remove( elem, name ); + } +} ); + +jQuery.fn.extend( { + data: function( key, value ) { + var i, name, data, + elem = this[ 0 ], + attrs = elem && elem.attributes; + + // Gets all values + if ( key === undefined ) { + if ( this.length ) { + data = dataUser.get( elem ); + + if ( elem.nodeType === 1 && !dataPriv.get( elem, "hasDataAttrs" ) ) { + i = attrs.length; + while ( i-- ) { + + // Support: IE 11 only + // The attrs elements can be null (#14894) + if ( attrs[ i ] ) { + name = attrs[ i ].name; + if ( name.indexOf( "data-" ) === 0 ) { + name = camelCase( name.slice( 5 ) ); + dataAttr( elem, name, data[ name ] ); + } + } + } + dataPriv.set( elem, "hasDataAttrs", true ); + } + } + + return data; + } + + // Sets multiple values + if ( typeof key === "object" ) { + return this.each( function() { + dataUser.set( this, key ); + } ); + } + + return access( this, function( value ) { + var data; + + // The calling jQuery object (element matches) is not empty + // (and therefore has an element appears at this[ 0 ]) and the + // `value` parameter was not undefined. An empty jQuery object + // will result in `undefined` for elem = this[ 0 ] which will + // throw an exception if an attempt to read a data cache is made. + if ( elem && value === undefined ) { + + // Attempt to get data from the cache + // The key will always be camelCased in Data + data = dataUser.get( elem, key ); + if ( data !== undefined ) { + return data; + } + + // Attempt to "discover" the data in + // HTML5 custom data-* attrs + data = dataAttr( elem, key ); + if ( data !== undefined ) { + return data; + } + + // We tried really hard, but the data doesn't exist. + return; + } + + // Set the data... + this.each( function() { + + // We always store the camelCased key + dataUser.set( this, key, value ); + } ); + }, null, value, arguments.length > 1, null, true ); + }, + + removeData: function( key ) { + return this.each( function() { + dataUser.remove( this, key ); + } ); + } +} ); + + +jQuery.extend( { + queue: function( elem, type, data ) { + var queue; + + if ( elem ) { + type = ( type || "fx" ) + "queue"; + queue = dataPriv.get( elem, type ); + + // Speed up dequeue by getting out quickly if this is just a lookup + if ( data ) { + if ( !queue || Array.isArray( data ) ) { + queue = dataPriv.access( elem, type, jQuery.makeArray( data ) ); + } else { + queue.push( data ); + } + } + return queue || []; + } + }, + + dequeue: function( elem, type ) { + type = type || "fx"; + + var queue = jQuery.queue( elem, type ), + startLength = queue.length, + fn = queue.shift(), + hooks = jQuery._queueHooks( elem, type ), + next = function() { + jQuery.dequeue( elem, type ); + }; + + // If the fx queue is dequeued, always remove the progress sentinel + if ( fn === "inprogress" ) { + fn = queue.shift(); + startLength--; + } + + if ( fn ) { + + // Add a progress sentinel to prevent the fx queue from being + // automatically dequeued + if ( type === "fx" ) { + queue.unshift( "inprogress" ); + } + + // Clear up the last queue stop function + delete hooks.stop; + fn.call( elem, next, hooks ); + } + + if ( !startLength && hooks ) { + hooks.empty.fire(); + } + }, + + // Not public - generate a queueHooks object, or return the current one + _queueHooks: function( elem, type ) { + var key = type + "queueHooks"; + return dataPriv.get( elem, key ) || dataPriv.access( elem, key, { + empty: jQuery.Callbacks( "once memory" ).add( function() { + dataPriv.remove( elem, [ type + "queue", key ] ); + } ) + } ); + } +} ); + +jQuery.fn.extend( { + queue: function( type, data ) { + var setter = 2; + + if ( typeof type !== "string" ) { + data = type; + type = "fx"; + setter--; + } + + if ( arguments.length < setter ) { + return jQuery.queue( this[ 0 ], type ); + } + + return data === undefined ? + this : + this.each( function() { + var queue = jQuery.queue( this, type, data ); + + // Ensure a hooks for this queue + jQuery._queueHooks( this, type ); + + if ( type === "fx" && queue[ 0 ] !== "inprogress" ) { + jQuery.dequeue( this, type ); + } + } ); + }, + dequeue: function( type ) { + return this.each( function() { + jQuery.dequeue( this, type ); + } ); + }, + clearQueue: function( type ) { + return this.queue( type || "fx", [] ); + }, + + // Get a promise resolved when queues of a certain type + // are emptied (fx is the type by default) + promise: function( type, obj ) { + var tmp, + count = 1, + defer = jQuery.Deferred(), + elements = this, + i = this.length, + resolve = function() { + if ( !( --count ) ) { + defer.resolveWith( elements, [ elements ] ); + } + }; + + if ( typeof type !== "string" ) { + obj = type; + type = undefined; + } + type = type || "fx"; + + while ( i-- ) { + tmp = dataPriv.get( elements[ i ], type + "queueHooks" ); + if ( tmp && tmp.empty ) { + count++; + tmp.empty.add( resolve ); + } + } + resolve(); + return defer.promise( obj ); + } +} ); +var pnum = ( /[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/ ).source; + +var rcssNum = new RegExp( "^(?:([+-])=|)(" + pnum + ")([a-z%]*)$", "i" ); + + +var cssExpand = [ "Top", "Right", "Bottom", "Left" ]; + +var documentElement = document.documentElement; + + + + var isAttached = function( elem ) { + return jQuery.contains( elem.ownerDocument, elem ); + }, + composed = { composed: true }; + + // Support: IE 9 - 11+, Edge 12 - 18+, iOS 10.0 - 10.2 only + // Check attachment across shadow DOM boundaries when possible (gh-3504) + // Support: iOS 10.0-10.2 only + // Early iOS 10 versions support `attachShadow` but not `getRootNode`, + // leading to errors. We need to check for `getRootNode`. + if ( documentElement.getRootNode ) { + isAttached = function( elem ) { + return jQuery.contains( elem.ownerDocument, elem ) || + elem.getRootNode( composed ) === elem.ownerDocument; + }; + } +var isHiddenWithinTree = function( elem, el ) { + + // isHiddenWithinTree might be called from jQuery#filter function; + // in that case, element will be second argument + elem = el || elem; + + // Inline style trumps all + return elem.style.display === "none" || + elem.style.display === "" && + + // Otherwise, check computed style + // Support: Firefox <=43 - 45 + // Disconnected elements can have computed display: none, so first confirm that elem is + // in the document. + isAttached( elem ) && + + jQuery.css( elem, "display" ) === "none"; + }; + + + +function adjustCSS( elem, prop, valueParts, tween ) { + var adjusted, scale, + maxIterations = 20, + currentValue = tween ? + function() { + return tween.cur(); + } : + function() { + return jQuery.css( elem, prop, "" ); + }, + initial = currentValue(), + unit = valueParts && valueParts[ 3 ] || ( jQuery.cssNumber[ prop ] ? "" : "px" ), + + // Starting value computation is required for potential unit mismatches + initialInUnit = elem.nodeType && + ( jQuery.cssNumber[ prop ] || unit !== "px" && +initial ) && + rcssNum.exec( jQuery.css( elem, prop ) ); + + if ( initialInUnit && initialInUnit[ 3 ] !== unit ) { + + // Support: Firefox <=54 + // Halve the iteration target value to prevent interference from CSS upper bounds (gh-2144) + initial = initial / 2; + + // Trust units reported by jQuery.css + unit = unit || initialInUnit[ 3 ]; + + // Iteratively approximate from a nonzero starting point + initialInUnit = +initial || 1; + + while ( maxIterations-- ) { + + // Evaluate and update our best guess (doubling guesses that zero out). + // Finish if the scale equals or crosses 1 (making the old*new product non-positive). + jQuery.style( elem, prop, initialInUnit + unit ); + if ( ( 1 - scale ) * ( 1 - ( scale = currentValue() / initial || 0.5 ) ) <= 0 ) { + maxIterations = 0; + } + initialInUnit = initialInUnit / scale; + + } + + initialInUnit = initialInUnit * 2; + jQuery.style( elem, prop, initialInUnit + unit ); + + // Make sure we update the tween properties later on + valueParts = valueParts || []; + } + + if ( valueParts ) { + initialInUnit = +initialInUnit || +initial || 0; + + // Apply relative offset (+=/-=) if specified + adjusted = valueParts[ 1 ] ? + initialInUnit + ( valueParts[ 1 ] + 1 ) * valueParts[ 2 ] : + +valueParts[ 2 ]; + if ( tween ) { + tween.unit = unit; + tween.start = initialInUnit; + tween.end = adjusted; + } + } + return adjusted; +} + + +var defaultDisplayMap = {}; + +function getDefaultDisplay( elem ) { + var temp, + doc = elem.ownerDocument, + nodeName = elem.nodeName, + display = defaultDisplayMap[ nodeName ]; + + if ( display ) { + return display; + } + + temp = doc.body.appendChild( doc.createElement( nodeName ) ); + display = jQuery.css( temp, "display" ); + + temp.parentNode.removeChild( temp ); + + if ( display === "none" ) { + display = "block"; + } + defaultDisplayMap[ nodeName ] = display; + + return display; +} + +function showHide( elements, show ) { + var display, elem, + values = [], + index = 0, + length = elements.length; + + // Determine new display value for elements that need to change + for ( ; index < length; index++ ) { + elem = elements[ index ]; + if ( !elem.style ) { + continue; + } + + display = elem.style.display; + if ( show ) { + + // Since we force visibility upon cascade-hidden elements, an immediate (and slow) + // check is required in this first loop unless we have a nonempty display value (either + // inline or about-to-be-restored) + if ( display === "none" ) { + values[ index ] = dataPriv.get( elem, "display" ) || null; + if ( !values[ index ] ) { + elem.style.display = ""; + } + } + if ( elem.style.display === "" && isHiddenWithinTree( elem ) ) { + values[ index ] = getDefaultDisplay( elem ); + } + } else { + if ( display !== "none" ) { + values[ index ] = "none"; + + // Remember what we're overwriting + dataPriv.set( elem, "display", display ); + } + } + } + + // Set the display of the elements in a second loop to avoid constant reflow + for ( index = 0; index < length; index++ ) { + if ( values[ index ] != null ) { + elements[ index ].style.display = values[ index ]; + } + } + + return elements; +} + +jQuery.fn.extend( { + show: function() { + return showHide( this, true ); + }, + hide: function() { + return showHide( this ); + }, + toggle: function( state ) { + if ( typeof state === "boolean" ) { + return state ? this.show() : this.hide(); + } + + return this.each( function() { + if ( isHiddenWithinTree( this ) ) { + jQuery( this ).show(); + } else { + jQuery( this ).hide(); + } + } ); + } +} ); +var rcheckableType = ( /^(?:checkbox|radio)$/i ); + +var rtagName = ( /<([a-z][^\/\0>\x20\t\r\n\f]*)/i ); + +var rscriptType = ( /^$|^module$|\/(?:java|ecma)script/i ); + + + +( function() { + var fragment = document.createDocumentFragment(), + div = fragment.appendChild( document.createElement( "div" ) ), + input = document.createElement( "input" ); + + // Support: Android 4.0 - 4.3 only + // Check state lost if the name is set (#11217) + // Support: Windows Web Apps (WWA) + // `name` and `type` must use .setAttribute for WWA (#14901) + input.setAttribute( "type", "radio" ); + input.setAttribute( "checked", "checked" ); + input.setAttribute( "name", "t" ); + + div.appendChild( input ); + + // Support: Android <=4.1 only + // Older WebKit doesn't clone checked state correctly in fragments + support.checkClone = div.cloneNode( true ).cloneNode( true ).lastChild.checked; + + // Support: IE <=11 only + // Make sure textarea (and checkbox) defaultValue is properly cloned + div.innerHTML = ""; + support.noCloneChecked = !!div.cloneNode( true ).lastChild.defaultValue; + + // Support: IE <=9 only + // IE <=9 replaces "; + support.option = !!div.lastChild; +} )(); + + +// We have to close these tags to support XHTML (#13200) +var wrapMap = { + + // XHTML parsers do not magically insert elements in the + // same way that tag soup parsers do. So we cannot shorten + // this by omitting or other required elements. + thead: [ 1, "", "
      " ], + col: [ 2, "", "
      " ], + tr: [ 2, "", "
      " ], + td: [ 3, "", "
      " ], + + _default: [ 0, "", "" ] +}; + +wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; +wrapMap.th = wrapMap.td; + +// Support: IE <=9 only +if ( !support.option ) { + wrapMap.optgroup = wrapMap.option = [ 1, "" ]; +} + + +function getAll( context, tag ) { + + // Support: IE <=9 - 11 only + // Use typeof to avoid zero-argument method invocation on host objects (#15151) + var ret; + + if ( typeof context.getElementsByTagName !== "undefined" ) { + ret = context.getElementsByTagName( tag || "*" ); + + } else if ( typeof context.querySelectorAll !== "undefined" ) { + ret = context.querySelectorAll( tag || "*" ); + + } else { + ret = []; + } + + if ( tag === undefined || tag && nodeName( context, tag ) ) { + return jQuery.merge( [ context ], ret ); + } + + return ret; +} + + +// Mark scripts as having already been evaluated +function setGlobalEval( elems, refElements ) { + var i = 0, + l = elems.length; + + for ( ; i < l; i++ ) { + dataPriv.set( + elems[ i ], + "globalEval", + !refElements || dataPriv.get( refElements[ i ], "globalEval" ) + ); + } +} + + +var rhtml = /<|&#?\w+;/; + +function buildFragment( elems, context, scripts, selection, ignored ) { + var elem, tmp, tag, wrap, attached, j, + fragment = context.createDocumentFragment(), + nodes = [], + i = 0, + l = elems.length; + + for ( ; i < l; i++ ) { + elem = elems[ i ]; + + if ( elem || elem === 0 ) { + + // Add nodes directly + if ( toType( elem ) === "object" ) { + + // Support: Android <=4.0 only, PhantomJS 1 only + // push.apply(_, arraylike) throws on ancient WebKit + jQuery.merge( nodes, elem.nodeType ? [ elem ] : elem ); + + // Convert non-html into a text node + } else if ( !rhtml.test( elem ) ) { + nodes.push( context.createTextNode( elem ) ); + + // Convert html into DOM nodes + } else { + tmp = tmp || fragment.appendChild( context.createElement( "div" ) ); + + // Deserialize a standard representation + tag = ( rtagName.exec( elem ) || [ "", "" ] )[ 1 ].toLowerCase(); + wrap = wrapMap[ tag ] || wrapMap._default; + tmp.innerHTML = wrap[ 1 ] + jQuery.htmlPrefilter( elem ) + wrap[ 2 ]; + + // Descend through wrappers to the right content + j = wrap[ 0 ]; + while ( j-- ) { + tmp = tmp.lastChild; + } + + // Support: Android <=4.0 only, PhantomJS 1 only + // push.apply(_, arraylike) throws on ancient WebKit + jQuery.merge( nodes, tmp.childNodes ); + + // Remember the top-level container + tmp = fragment.firstChild; + + // Ensure the created nodes are orphaned (#12392) + tmp.textContent = ""; + } + } + } + + // Remove wrapper from fragment + fragment.textContent = ""; + + i = 0; + while ( ( elem = nodes[ i++ ] ) ) { + + // Skip elements already in the context collection (trac-4087) + if ( selection && jQuery.inArray( elem, selection ) > -1 ) { + if ( ignored ) { + ignored.push( elem ); + } + continue; + } + + attached = isAttached( elem ); + + // Append to fragment + tmp = getAll( fragment.appendChild( elem ), "script" ); + + // Preserve script evaluation history + if ( attached ) { + setGlobalEval( tmp ); + } + + // Capture executables + if ( scripts ) { + j = 0; + while ( ( elem = tmp[ j++ ] ) ) { + if ( rscriptType.test( elem.type || "" ) ) { + scripts.push( elem ); + } + } + } + } + + return fragment; +} + + +var rtypenamespace = /^([^.]*)(?:\.(.+)|)/; + +function returnTrue() { + return true; +} + +function returnFalse() { + return false; +} + +// Support: IE <=9 - 11+ +// focus() and blur() are asynchronous, except when they are no-op. +// So expect focus to be synchronous when the element is already active, +// and blur to be synchronous when the element is not already active. +// (focus and blur are always synchronous in other supported browsers, +// this just defines when we can count on it). +function expectSync( elem, type ) { + return ( elem === safeActiveElement() ) === ( type === "focus" ); +} + +// Support: IE <=9 only +// Accessing document.activeElement can throw unexpectedly +// https://bugs.jquery.com/ticket/13393 +function safeActiveElement() { + try { + return document.activeElement; + } catch ( err ) { } +} + +function on( elem, types, selector, data, fn, one ) { + var origFn, type; + + // Types can be a map of types/handlers + if ( typeof types === "object" ) { + + // ( types-Object, selector, data ) + if ( typeof selector !== "string" ) { + + // ( types-Object, data ) + data = data || selector; + selector = undefined; + } + for ( type in types ) { + on( elem, type, selector, data, types[ type ], one ); + } + return elem; + } + + if ( data == null && fn == null ) { + + // ( types, fn ) + fn = selector; + data = selector = undefined; + } else if ( fn == null ) { + if ( typeof selector === "string" ) { + + // ( types, selector, fn ) + fn = data; + data = undefined; + } else { + + // ( types, data, fn ) + fn = data; + data = selector; + selector = undefined; + } + } + if ( fn === false ) { + fn = returnFalse; + } else if ( !fn ) { + return elem; + } + + if ( one === 1 ) { + origFn = fn; + fn = function( event ) { + + // Can use an empty set, since event contains the info + jQuery().off( event ); + return origFn.apply( this, arguments ); + }; + + // Use same guid so caller can remove using origFn + fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ ); + } + return elem.each( function() { + jQuery.event.add( this, types, fn, data, selector ); + } ); +} + +/* + * Helper functions for managing events -- not part of the public interface. + * Props to Dean Edwards' addEvent library for many of the ideas. + */ +jQuery.event = { + + global: {}, + + add: function( elem, types, handler, data, selector ) { + + var handleObjIn, eventHandle, tmp, + events, t, handleObj, + special, handlers, type, namespaces, origType, + elemData = dataPriv.get( elem ); + + // Only attach events to objects that accept data + if ( !acceptData( elem ) ) { + return; + } + + // Caller can pass in an object of custom data in lieu of the handler + if ( handler.handler ) { + handleObjIn = handler; + handler = handleObjIn.handler; + selector = handleObjIn.selector; + } + + // Ensure that invalid selectors throw exceptions at attach time + // Evaluate against documentElement in case elem is a non-element node (e.g., document) + if ( selector ) { + jQuery.find.matchesSelector( documentElement, selector ); + } + + // Make sure that the handler has a unique ID, used to find/remove it later + if ( !handler.guid ) { + handler.guid = jQuery.guid++; + } + + // Init the element's event structure and main handler, if this is the first + if ( !( events = elemData.events ) ) { + events = elemData.events = Object.create( null ); + } + if ( !( eventHandle = elemData.handle ) ) { + eventHandle = elemData.handle = function( e ) { + + // Discard the second event of a jQuery.event.trigger() and + // when an event is called after a page has unloaded + return typeof jQuery !== "undefined" && jQuery.event.triggered !== e.type ? + jQuery.event.dispatch.apply( elem, arguments ) : undefined; + }; + } + + // Handle multiple events separated by a space + types = ( types || "" ).match( rnothtmlwhite ) || [ "" ]; + t = types.length; + while ( t-- ) { + tmp = rtypenamespace.exec( types[ t ] ) || []; + type = origType = tmp[ 1 ]; + namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort(); + + // There *must* be a type, no attaching namespace-only handlers + if ( !type ) { + continue; + } + + // If event changes its type, use the special event handlers for the changed type + special = jQuery.event.special[ type ] || {}; + + // If selector defined, determine special event api type, otherwise given type + type = ( selector ? special.delegateType : special.bindType ) || type; + + // Update special based on newly reset type + special = jQuery.event.special[ type ] || {}; + + // handleObj is passed to all event handlers + handleObj = jQuery.extend( { + type: type, + origType: origType, + data: data, + handler: handler, + guid: handler.guid, + selector: selector, + needsContext: selector && jQuery.expr.match.needsContext.test( selector ), + namespace: namespaces.join( "." ) + }, handleObjIn ); + + // Init the event handler queue if we're the first + if ( !( handlers = events[ type ] ) ) { + handlers = events[ type ] = []; + handlers.delegateCount = 0; + + // Only use addEventListener if the special events handler returns false + if ( !special.setup || + special.setup.call( elem, data, namespaces, eventHandle ) === false ) { + + if ( elem.addEventListener ) { + elem.addEventListener( type, eventHandle ); + } + } + } + + if ( special.add ) { + special.add.call( elem, handleObj ); + + if ( !handleObj.handler.guid ) { + handleObj.handler.guid = handler.guid; + } + } + + // Add to the element's handler list, delegates in front + if ( selector ) { + handlers.splice( handlers.delegateCount++, 0, handleObj ); + } else { + handlers.push( handleObj ); + } + + // Keep track of which events have ever been used, for event optimization + jQuery.event.global[ type ] = true; + } + + }, + + // Detach an event or set of events from an element + remove: function( elem, types, handler, selector, mappedTypes ) { + + var j, origCount, tmp, + events, t, handleObj, + special, handlers, type, namespaces, origType, + elemData = dataPriv.hasData( elem ) && dataPriv.get( elem ); + + if ( !elemData || !( events = elemData.events ) ) { + return; + } + + // Once for each type.namespace in types; type may be omitted + types = ( types || "" ).match( rnothtmlwhite ) || [ "" ]; + t = types.length; + while ( t-- ) { + tmp = rtypenamespace.exec( types[ t ] ) || []; + type = origType = tmp[ 1 ]; + namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort(); + + // Unbind all events (on this namespace, if provided) for the element + if ( !type ) { + for ( type in events ) { + jQuery.event.remove( elem, type + types[ t ], handler, selector, true ); + } + continue; + } + + special = jQuery.event.special[ type ] || {}; + type = ( selector ? special.delegateType : special.bindType ) || type; + handlers = events[ type ] || []; + tmp = tmp[ 2 ] && + new RegExp( "(^|\\.)" + namespaces.join( "\\.(?:.*\\.|)" ) + "(\\.|$)" ); + + // Remove matching events + origCount = j = handlers.length; + while ( j-- ) { + handleObj = handlers[ j ]; + + if ( ( mappedTypes || origType === handleObj.origType ) && + ( !handler || handler.guid === handleObj.guid ) && + ( !tmp || tmp.test( handleObj.namespace ) ) && + ( !selector || selector === handleObj.selector || + selector === "**" && handleObj.selector ) ) { + handlers.splice( j, 1 ); + + if ( handleObj.selector ) { + handlers.delegateCount--; + } + if ( special.remove ) { + special.remove.call( elem, handleObj ); + } + } + } + + // Remove generic event handler if we removed something and no more handlers exist + // (avoids potential for endless recursion during removal of special event handlers) + if ( origCount && !handlers.length ) { + if ( !special.teardown || + special.teardown.call( elem, namespaces, elemData.handle ) === false ) { + + jQuery.removeEvent( elem, type, elemData.handle ); + } + + delete events[ type ]; + } + } + + // Remove data and the expando if it's no longer used + if ( jQuery.isEmptyObject( events ) ) { + dataPriv.remove( elem, "handle events" ); + } + }, + + dispatch: function( nativeEvent ) { + + var i, j, ret, matched, handleObj, handlerQueue, + args = new Array( arguments.length ), + + // Make a writable jQuery.Event from the native event object + event = jQuery.event.fix( nativeEvent ), + + handlers = ( + dataPriv.get( this, "events" ) || Object.create( null ) + )[ event.type ] || [], + special = jQuery.event.special[ event.type ] || {}; + + // Use the fix-ed jQuery.Event rather than the (read-only) native event + args[ 0 ] = event; + + for ( i = 1; i < arguments.length; i++ ) { + args[ i ] = arguments[ i ]; + } + + event.delegateTarget = this; + + // Call the preDispatch hook for the mapped type, and let it bail if desired + if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) { + return; + } + + // Determine handlers + handlerQueue = jQuery.event.handlers.call( this, event, handlers ); + + // Run delegates first; they may want to stop propagation beneath us + i = 0; + while ( ( matched = handlerQueue[ i++ ] ) && !event.isPropagationStopped() ) { + event.currentTarget = matched.elem; + + j = 0; + while ( ( handleObj = matched.handlers[ j++ ] ) && + !event.isImmediatePropagationStopped() ) { + + // If the event is namespaced, then each handler is only invoked if it is + // specially universal or its namespaces are a superset of the event's. + if ( !event.rnamespace || handleObj.namespace === false || + event.rnamespace.test( handleObj.namespace ) ) { + + event.handleObj = handleObj; + event.data = handleObj.data; + + ret = ( ( jQuery.event.special[ handleObj.origType ] || {} ).handle || + handleObj.handler ).apply( matched.elem, args ); + + if ( ret !== undefined ) { + if ( ( event.result = ret ) === false ) { + event.preventDefault(); + event.stopPropagation(); + } + } + } + } + } + + // Call the postDispatch hook for the mapped type + if ( special.postDispatch ) { + special.postDispatch.call( this, event ); + } + + return event.result; + }, + + handlers: function( event, handlers ) { + var i, handleObj, sel, matchedHandlers, matchedSelectors, + handlerQueue = [], + delegateCount = handlers.delegateCount, + cur = event.target; + + // Find delegate handlers + if ( delegateCount && + + // Support: IE <=9 + // Black-hole SVG instance trees (trac-13180) + cur.nodeType && + + // Support: Firefox <=42 + // Suppress spec-violating clicks indicating a non-primary pointer button (trac-3861) + // https://www.w3.org/TR/DOM-Level-3-Events/#event-type-click + // Support: IE 11 only + // ...but not arrow key "clicks" of radio inputs, which can have `button` -1 (gh-2343) + !( event.type === "click" && event.button >= 1 ) ) { + + for ( ; cur !== this; cur = cur.parentNode || this ) { + + // Don't check non-elements (#13208) + // Don't process clicks on disabled elements (#6911, #8165, #11382, #11764) + if ( cur.nodeType === 1 && !( event.type === "click" && cur.disabled === true ) ) { + matchedHandlers = []; + matchedSelectors = {}; + for ( i = 0; i < delegateCount; i++ ) { + handleObj = handlers[ i ]; + + // Don't conflict with Object.prototype properties (#13203) + sel = handleObj.selector + " "; + + if ( matchedSelectors[ sel ] === undefined ) { + matchedSelectors[ sel ] = handleObj.needsContext ? + jQuery( sel, this ).index( cur ) > -1 : + jQuery.find( sel, this, null, [ cur ] ).length; + } + if ( matchedSelectors[ sel ] ) { + matchedHandlers.push( handleObj ); + } + } + if ( matchedHandlers.length ) { + handlerQueue.push( { elem: cur, handlers: matchedHandlers } ); + } + } + } + } + + // Add the remaining (directly-bound) handlers + cur = this; + if ( delegateCount < handlers.length ) { + handlerQueue.push( { elem: cur, handlers: handlers.slice( delegateCount ) } ); + } + + return handlerQueue; + }, + + addProp: function( name, hook ) { + Object.defineProperty( jQuery.Event.prototype, name, { + enumerable: true, + configurable: true, + + get: isFunction( hook ) ? + function() { + if ( this.originalEvent ) { + return hook( this.originalEvent ); + } + } : + function() { + if ( this.originalEvent ) { + return this.originalEvent[ name ]; + } + }, + + set: function( value ) { + Object.defineProperty( this, name, { + enumerable: true, + configurable: true, + writable: true, + value: value + } ); + } + } ); + }, + + fix: function( originalEvent ) { + return originalEvent[ jQuery.expando ] ? + originalEvent : + new jQuery.Event( originalEvent ); + }, + + special: { + load: { + + // Prevent triggered image.load events from bubbling to window.load + noBubble: true + }, + click: { + + // Utilize native event to ensure correct state for checkable inputs + setup: function( data ) { + + // For mutual compressibility with _default, replace `this` access with a local var. + // `|| data` is dead code meant only to preserve the variable through minification. + var el = this || data; + + // Claim the first handler + if ( rcheckableType.test( el.type ) && + el.click && nodeName( el, "input" ) ) { + + // dataPriv.set( el, "click", ... ) + leverageNative( el, "click", returnTrue ); + } + + // Return false to allow normal processing in the caller + return false; + }, + trigger: function( data ) { + + // For mutual compressibility with _default, replace `this` access with a local var. + // `|| data` is dead code meant only to preserve the variable through minification. + var el = this || data; + + // Force setup before triggering a click + if ( rcheckableType.test( el.type ) && + el.click && nodeName( el, "input" ) ) { + + leverageNative( el, "click" ); + } + + // Return non-false to allow normal event-path propagation + return true; + }, + + // For cross-browser consistency, suppress native .click() on links + // Also prevent it if we're currently inside a leveraged native-event stack + _default: function( event ) { + var target = event.target; + return rcheckableType.test( target.type ) && + target.click && nodeName( target, "input" ) && + dataPriv.get( target, "click" ) || + nodeName( target, "a" ); + } + }, + + beforeunload: { + postDispatch: function( event ) { + + // Support: Firefox 20+ + // Firefox doesn't alert if the returnValue field is not set. + if ( event.result !== undefined && event.originalEvent ) { + event.originalEvent.returnValue = event.result; + } + } + } + } +}; + +// Ensure the presence of an event listener that handles manually-triggered +// synthetic events by interrupting progress until reinvoked in response to +// *native* events that it fires directly, ensuring that state changes have +// already occurred before other listeners are invoked. +function leverageNative( el, type, expectSync ) { + + // Missing expectSync indicates a trigger call, which must force setup through jQuery.event.add + if ( !expectSync ) { + if ( dataPriv.get( el, type ) === undefined ) { + jQuery.event.add( el, type, returnTrue ); + } + return; + } + + // Register the controller as a special universal handler for all event namespaces + dataPriv.set( el, type, false ); + jQuery.event.add( el, type, { + namespace: false, + handler: function( event ) { + var notAsync, result, + saved = dataPriv.get( this, type ); + + if ( ( event.isTrigger & 1 ) && this[ type ] ) { + + // Interrupt processing of the outer synthetic .trigger()ed event + // Saved data should be false in such cases, but might be a leftover capture object + // from an async native handler (gh-4350) + if ( !saved.length ) { + + // Store arguments for use when handling the inner native event + // There will always be at least one argument (an event object), so this array + // will not be confused with a leftover capture object. + saved = slice.call( arguments ); + dataPriv.set( this, type, saved ); + + // Trigger the native event and capture its result + // Support: IE <=9 - 11+ + // focus() and blur() are asynchronous + notAsync = expectSync( this, type ); + this[ type ](); + result = dataPriv.get( this, type ); + if ( saved !== result || notAsync ) { + dataPriv.set( this, type, false ); + } else { + result = {}; + } + if ( saved !== result ) { + + // Cancel the outer synthetic event + event.stopImmediatePropagation(); + event.preventDefault(); + + // Support: Chrome 86+ + // In Chrome, if an element having a focusout handler is blurred by + // clicking outside of it, it invokes the handler synchronously. If + // that handler calls `.remove()` on the element, the data is cleared, + // leaving `result` undefined. We need to guard against this. + return result && result.value; + } + + // If this is an inner synthetic event for an event with a bubbling surrogate + // (focus or blur), assume that the surrogate already propagated from triggering the + // native event and prevent that from happening again here. + // This technically gets the ordering wrong w.r.t. to `.trigger()` (in which the + // bubbling surrogate propagates *after* the non-bubbling base), but that seems + // less bad than duplication. + } else if ( ( jQuery.event.special[ type ] || {} ).delegateType ) { + event.stopPropagation(); + } + + // If this is a native event triggered above, everything is now in order + // Fire an inner synthetic event with the original arguments + } else if ( saved.length ) { + + // ...and capture the result + dataPriv.set( this, type, { + value: jQuery.event.trigger( + + // Support: IE <=9 - 11+ + // Extend with the prototype to reset the above stopImmediatePropagation() + jQuery.extend( saved[ 0 ], jQuery.Event.prototype ), + saved.slice( 1 ), + this + ) + } ); + + // Abort handling of the native event + event.stopImmediatePropagation(); + } + } + } ); +} + +jQuery.removeEvent = function( elem, type, handle ) { + + // This "if" is needed for plain objects + if ( elem.removeEventListener ) { + elem.removeEventListener( type, handle ); + } +}; + +jQuery.Event = function( src, props ) { + + // Allow instantiation without the 'new' keyword + if ( !( this instanceof jQuery.Event ) ) { + return new jQuery.Event( src, props ); + } + + // Event object + if ( src && src.type ) { + this.originalEvent = src; + this.type = src.type; + + // Events bubbling up the document may have been marked as prevented + // by a handler lower down the tree; reflect the correct value. + this.isDefaultPrevented = src.defaultPrevented || + src.defaultPrevented === undefined && + + // Support: Android <=2.3 only + src.returnValue === false ? + returnTrue : + returnFalse; + + // Create target properties + // Support: Safari <=6 - 7 only + // Target should not be a text node (#504, #13143) + this.target = ( src.target && src.target.nodeType === 3 ) ? + src.target.parentNode : + src.target; + + this.currentTarget = src.currentTarget; + this.relatedTarget = src.relatedTarget; + + // Event type + } else { + this.type = src; + } + + // Put explicitly provided properties onto the event object + if ( props ) { + jQuery.extend( this, props ); + } + + // Create a timestamp if incoming event doesn't have one + this.timeStamp = src && src.timeStamp || Date.now(); + + // Mark it as fixed + this[ jQuery.expando ] = true; +}; + +// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding +// https://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html +jQuery.Event.prototype = { + constructor: jQuery.Event, + isDefaultPrevented: returnFalse, + isPropagationStopped: returnFalse, + isImmediatePropagationStopped: returnFalse, + isSimulated: false, + + preventDefault: function() { + var e = this.originalEvent; + + this.isDefaultPrevented = returnTrue; + + if ( e && !this.isSimulated ) { + e.preventDefault(); + } + }, + stopPropagation: function() { + var e = this.originalEvent; + + this.isPropagationStopped = returnTrue; + + if ( e && !this.isSimulated ) { + e.stopPropagation(); + } + }, + stopImmediatePropagation: function() { + var e = this.originalEvent; + + this.isImmediatePropagationStopped = returnTrue; + + if ( e && !this.isSimulated ) { + e.stopImmediatePropagation(); + } + + this.stopPropagation(); + } +}; + +// Includes all common event props including KeyEvent and MouseEvent specific props +jQuery.each( { + altKey: true, + bubbles: true, + cancelable: true, + changedTouches: true, + ctrlKey: true, + detail: true, + eventPhase: true, + metaKey: true, + pageX: true, + pageY: true, + shiftKey: true, + view: true, + "char": true, + code: true, + charCode: true, + key: true, + keyCode: true, + button: true, + buttons: true, + clientX: true, + clientY: true, + offsetX: true, + offsetY: true, + pointerId: true, + pointerType: true, + screenX: true, + screenY: true, + targetTouches: true, + toElement: true, + touches: true, + which: true +}, jQuery.event.addProp ); + +jQuery.each( { focus: "focusin", blur: "focusout" }, function( type, delegateType ) { + jQuery.event.special[ type ] = { + + // Utilize native event if possible so blur/focus sequence is correct + setup: function() { + + // Claim the first handler + // dataPriv.set( this, "focus", ... ) + // dataPriv.set( this, "blur", ... ) + leverageNative( this, type, expectSync ); + + // Return false to allow normal processing in the caller + return false; + }, + trigger: function() { + + // Force setup before trigger + leverageNative( this, type ); + + // Return non-false to allow normal event-path propagation + return true; + }, + + // Suppress native focus or blur as it's already being fired + // in leverageNative. + _default: function() { + return true; + }, + + delegateType: delegateType + }; +} ); + +// Create mouseenter/leave events using mouseover/out and event-time checks +// so that event delegation works in jQuery. +// Do the same for pointerenter/pointerleave and pointerover/pointerout +// +// Support: Safari 7 only +// Safari sends mouseenter too often; see: +// https://bugs.chromium.org/p/chromium/issues/detail?id=470258 +// for the description of the bug (it existed in older Chrome versions as well). +jQuery.each( { + mouseenter: "mouseover", + mouseleave: "mouseout", + pointerenter: "pointerover", + pointerleave: "pointerout" +}, function( orig, fix ) { + jQuery.event.special[ orig ] = { + delegateType: fix, + bindType: fix, + + handle: function( event ) { + var ret, + target = this, + related = event.relatedTarget, + handleObj = event.handleObj; + + // For mouseenter/leave call the handler if related is outside the target. + // NB: No relatedTarget if the mouse left/entered the browser window + if ( !related || ( related !== target && !jQuery.contains( target, related ) ) ) { + event.type = handleObj.origType; + ret = handleObj.handler.apply( this, arguments ); + event.type = fix; + } + return ret; + } + }; +} ); + +jQuery.fn.extend( { + + on: function( types, selector, data, fn ) { + return on( this, types, selector, data, fn ); + }, + one: function( types, selector, data, fn ) { + return on( this, types, selector, data, fn, 1 ); + }, + off: function( types, selector, fn ) { + var handleObj, type; + if ( types && types.preventDefault && types.handleObj ) { + + // ( event ) dispatched jQuery.Event + handleObj = types.handleObj; + jQuery( types.delegateTarget ).off( + handleObj.namespace ? + handleObj.origType + "." + handleObj.namespace : + handleObj.origType, + handleObj.selector, + handleObj.handler + ); + return this; + } + if ( typeof types === "object" ) { + + // ( types-object [, selector] ) + for ( type in types ) { + this.off( type, selector, types[ type ] ); + } + return this; + } + if ( selector === false || typeof selector === "function" ) { + + // ( types [, fn] ) + fn = selector; + selector = undefined; + } + if ( fn === false ) { + fn = returnFalse; + } + return this.each( function() { + jQuery.event.remove( this, types, fn, selector ); + } ); + } +} ); + + +var + + // Support: IE <=10 - 11, Edge 12 - 13 only + // In IE/Edge using regex groups here causes severe slowdowns. + // See https://connect.microsoft.com/IE/feedback/details/1736512/ + rnoInnerhtml = /\s*$/g; + +// Prefer a tbody over its parent table for containing new rows +function manipulationTarget( elem, content ) { + if ( nodeName( elem, "table" ) && + nodeName( content.nodeType !== 11 ? content : content.firstChild, "tr" ) ) { + + return jQuery( elem ).children( "tbody" )[ 0 ] || elem; + } + + return elem; +} + +// Replace/restore the type attribute of script elements for safe DOM manipulation +function disableScript( elem ) { + elem.type = ( elem.getAttribute( "type" ) !== null ) + "/" + elem.type; + return elem; +} +function restoreScript( elem ) { + if ( ( elem.type || "" ).slice( 0, 5 ) === "true/" ) { + elem.type = elem.type.slice( 5 ); + } else { + elem.removeAttribute( "type" ); + } + + return elem; +} + +function cloneCopyEvent( src, dest ) { + var i, l, type, pdataOld, udataOld, udataCur, events; + + if ( dest.nodeType !== 1 ) { + return; + } + + // 1. Copy private data: events, handlers, etc. + if ( dataPriv.hasData( src ) ) { + pdataOld = dataPriv.get( src ); + events = pdataOld.events; + + if ( events ) { + dataPriv.remove( dest, "handle events" ); + + for ( type in events ) { + for ( i = 0, l = events[ type ].length; i < l; i++ ) { + jQuery.event.add( dest, type, events[ type ][ i ] ); + } + } + } + } + + // 2. Copy user data + if ( dataUser.hasData( src ) ) { + udataOld = dataUser.access( src ); + udataCur = jQuery.extend( {}, udataOld ); + + dataUser.set( dest, udataCur ); + } +} + +// Fix IE bugs, see support tests +function fixInput( src, dest ) { + var nodeName = dest.nodeName.toLowerCase(); + + // Fails to persist the checked state of a cloned checkbox or radio button. + if ( nodeName === "input" && rcheckableType.test( src.type ) ) { + dest.checked = src.checked; + + // Fails to return the selected option to the default selected state when cloning options + } else if ( nodeName === "input" || nodeName === "textarea" ) { + dest.defaultValue = src.defaultValue; + } +} + +function domManip( collection, args, callback, ignored ) { + + // Flatten any nested arrays + args = flat( args ); + + var fragment, first, scripts, hasScripts, node, doc, + i = 0, + l = collection.length, + iNoClone = l - 1, + value = args[ 0 ], + valueIsFunction = isFunction( value ); + + // We can't cloneNode fragments that contain checked, in WebKit + if ( valueIsFunction || + ( l > 1 && typeof value === "string" && + !support.checkClone && rchecked.test( value ) ) ) { + return collection.each( function( index ) { + var self = collection.eq( index ); + if ( valueIsFunction ) { + args[ 0 ] = value.call( this, index, self.html() ); + } + domManip( self, args, callback, ignored ); + } ); + } + + if ( l ) { + fragment = buildFragment( args, collection[ 0 ].ownerDocument, false, collection, ignored ); + first = fragment.firstChild; + + if ( fragment.childNodes.length === 1 ) { + fragment = first; + } + + // Require either new content or an interest in ignored elements to invoke the callback + if ( first || ignored ) { + scripts = jQuery.map( getAll( fragment, "script" ), disableScript ); + hasScripts = scripts.length; + + // Use the original fragment for the last item + // instead of the first because it can end up + // being emptied incorrectly in certain situations (#8070). + for ( ; i < l; i++ ) { + node = fragment; + + if ( i !== iNoClone ) { + node = jQuery.clone( node, true, true ); + + // Keep references to cloned scripts for later restoration + if ( hasScripts ) { + + // Support: Android <=4.0 only, PhantomJS 1 only + // push.apply(_, arraylike) throws on ancient WebKit + jQuery.merge( scripts, getAll( node, "script" ) ); + } + } + + callback.call( collection[ i ], node, i ); + } + + if ( hasScripts ) { + doc = scripts[ scripts.length - 1 ].ownerDocument; + + // Reenable scripts + jQuery.map( scripts, restoreScript ); + + // Evaluate executable scripts on first document insertion + for ( i = 0; i < hasScripts; i++ ) { + node = scripts[ i ]; + if ( rscriptType.test( node.type || "" ) && + !dataPriv.access( node, "globalEval" ) && + jQuery.contains( doc, node ) ) { + + if ( node.src && ( node.type || "" ).toLowerCase() !== "module" ) { + + // Optional AJAX dependency, but won't run scripts if not present + if ( jQuery._evalUrl && !node.noModule ) { + jQuery._evalUrl( node.src, { + nonce: node.nonce || node.getAttribute( "nonce" ) + }, doc ); + } + } else { + DOMEval( node.textContent.replace( rcleanScript, "" ), node, doc ); + } + } + } + } + } + } + + return collection; +} + +function remove( elem, selector, keepData ) { + var node, + nodes = selector ? jQuery.filter( selector, elem ) : elem, + i = 0; + + for ( ; ( node = nodes[ i ] ) != null; i++ ) { + if ( !keepData && node.nodeType === 1 ) { + jQuery.cleanData( getAll( node ) ); + } + + if ( node.parentNode ) { + if ( keepData && isAttached( node ) ) { + setGlobalEval( getAll( node, "script" ) ); + } + node.parentNode.removeChild( node ); + } + } + + return elem; +} + +jQuery.extend( { + htmlPrefilter: function( html ) { + return html; + }, + + clone: function( elem, dataAndEvents, deepDataAndEvents ) { + var i, l, srcElements, destElements, + clone = elem.cloneNode( true ), + inPage = isAttached( elem ); + + // Fix IE cloning issues + if ( !support.noCloneChecked && ( elem.nodeType === 1 || elem.nodeType === 11 ) && + !jQuery.isXMLDoc( elem ) ) { + + // We eschew Sizzle here for performance reasons: https://jsperf.com/getall-vs-sizzle/2 + destElements = getAll( clone ); + srcElements = getAll( elem ); + + for ( i = 0, l = srcElements.length; i < l; i++ ) { + fixInput( srcElements[ i ], destElements[ i ] ); + } + } + + // Copy the events from the original to the clone + if ( dataAndEvents ) { + if ( deepDataAndEvents ) { + srcElements = srcElements || getAll( elem ); + destElements = destElements || getAll( clone ); + + for ( i = 0, l = srcElements.length; i < l; i++ ) { + cloneCopyEvent( srcElements[ i ], destElements[ i ] ); + } + } else { + cloneCopyEvent( elem, clone ); + } + } + + // Preserve script evaluation history + destElements = getAll( clone, "script" ); + if ( destElements.length > 0 ) { + setGlobalEval( destElements, !inPage && getAll( elem, "script" ) ); + } + + // Return the cloned set + return clone; + }, + + cleanData: function( elems ) { + var data, elem, type, + special = jQuery.event.special, + i = 0; + + for ( ; ( elem = elems[ i ] ) !== undefined; i++ ) { + if ( acceptData( elem ) ) { + if ( ( data = elem[ dataPriv.expando ] ) ) { + if ( data.events ) { + for ( type in data.events ) { + if ( special[ type ] ) { + jQuery.event.remove( elem, type ); + + // This is a shortcut to avoid jQuery.event.remove's overhead + } else { + jQuery.removeEvent( elem, type, data.handle ); + } + } + } + + // Support: Chrome <=35 - 45+ + // Assign undefined instead of using delete, see Data#remove + elem[ dataPriv.expando ] = undefined; + } + if ( elem[ dataUser.expando ] ) { + + // Support: Chrome <=35 - 45+ + // Assign undefined instead of using delete, see Data#remove + elem[ dataUser.expando ] = undefined; + } + } + } + } +} ); + +jQuery.fn.extend( { + detach: function( selector ) { + return remove( this, selector, true ); + }, + + remove: function( selector ) { + return remove( this, selector ); + }, + + text: function( value ) { + return access( this, function( value ) { + return value === undefined ? + jQuery.text( this ) : + this.empty().each( function() { + if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { + this.textContent = value; + } + } ); + }, null, value, arguments.length ); + }, + + append: function() { + return domManip( this, arguments, function( elem ) { + if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { + var target = manipulationTarget( this, elem ); + target.appendChild( elem ); + } + } ); + }, + + prepend: function() { + return domManip( this, arguments, function( elem ) { + if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { + var target = manipulationTarget( this, elem ); + target.insertBefore( elem, target.firstChild ); + } + } ); + }, + + before: function() { + return domManip( this, arguments, function( elem ) { + if ( this.parentNode ) { + this.parentNode.insertBefore( elem, this ); + } + } ); + }, + + after: function() { + return domManip( this, arguments, function( elem ) { + if ( this.parentNode ) { + this.parentNode.insertBefore( elem, this.nextSibling ); + } + } ); + }, + + empty: function() { + var elem, + i = 0; + + for ( ; ( elem = this[ i ] ) != null; i++ ) { + if ( elem.nodeType === 1 ) { + + // Prevent memory leaks + jQuery.cleanData( getAll( elem, false ) ); + + // Remove any remaining nodes + elem.textContent = ""; + } + } + + return this; + }, + + clone: function( dataAndEvents, deepDataAndEvents ) { + dataAndEvents = dataAndEvents == null ? false : dataAndEvents; + deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents; + + return this.map( function() { + return jQuery.clone( this, dataAndEvents, deepDataAndEvents ); + } ); + }, + + html: function( value ) { + return access( this, function( value ) { + var elem = this[ 0 ] || {}, + i = 0, + l = this.length; + + if ( value === undefined && elem.nodeType === 1 ) { + return elem.innerHTML; + } + + // See if we can take a shortcut and just use innerHTML + if ( typeof value === "string" && !rnoInnerhtml.test( value ) && + !wrapMap[ ( rtagName.exec( value ) || [ "", "" ] )[ 1 ].toLowerCase() ] ) { + + value = jQuery.htmlPrefilter( value ); + + try { + for ( ; i < l; i++ ) { + elem = this[ i ] || {}; + + // Remove element nodes and prevent memory leaks + if ( elem.nodeType === 1 ) { + jQuery.cleanData( getAll( elem, false ) ); + elem.innerHTML = value; + } + } + + elem = 0; + + // If using innerHTML throws an exception, use the fallback method + } catch ( e ) {} + } + + if ( elem ) { + this.empty().append( value ); + } + }, null, value, arguments.length ); + }, + + replaceWith: function() { + var ignored = []; + + // Make the changes, replacing each non-ignored context element with the new content + return domManip( this, arguments, function( elem ) { + var parent = this.parentNode; + + if ( jQuery.inArray( this, ignored ) < 0 ) { + jQuery.cleanData( getAll( this ) ); + if ( parent ) { + parent.replaceChild( elem, this ); + } + } + + // Force callback invocation + }, ignored ); + } +} ); + +jQuery.each( { + appendTo: "append", + prependTo: "prepend", + insertBefore: "before", + insertAfter: "after", + replaceAll: "replaceWith" +}, function( name, original ) { + jQuery.fn[ name ] = function( selector ) { + var elems, + ret = [], + insert = jQuery( selector ), + last = insert.length - 1, + i = 0; + + for ( ; i <= last; i++ ) { + elems = i === last ? this : this.clone( true ); + jQuery( insert[ i ] )[ original ]( elems ); + + // Support: Android <=4.0 only, PhantomJS 1 only + // .get() because push.apply(_, arraylike) throws on ancient WebKit + push.apply( ret, elems.get() ); + } + + return this.pushStack( ret ); + }; +} ); +var rnumnonpx = new RegExp( "^(" + pnum + ")(?!px)[a-z%]+$", "i" ); + +var getStyles = function( elem ) { + + // Support: IE <=11 only, Firefox <=30 (#15098, #14150) + // IE throws on elements created in popups + // FF meanwhile throws on frame elements through "defaultView.getComputedStyle" + var view = elem.ownerDocument.defaultView; + + if ( !view || !view.opener ) { + view = window; + } + + return view.getComputedStyle( elem ); + }; + +var swap = function( elem, options, callback ) { + var ret, name, + old = {}; + + // Remember the old values, and insert the new ones + for ( name in options ) { + old[ name ] = elem.style[ name ]; + elem.style[ name ] = options[ name ]; + } + + ret = callback.call( elem ); + + // Revert the old values + for ( name in options ) { + elem.style[ name ] = old[ name ]; + } + + return ret; +}; + + +var rboxStyle = new RegExp( cssExpand.join( "|" ), "i" ); + + + +( function() { + + // Executing both pixelPosition & boxSizingReliable tests require only one layout + // so they're executed at the same time to save the second computation. + function computeStyleTests() { + + // This is a singleton, we need to execute it only once + if ( !div ) { + return; + } + + container.style.cssText = "position:absolute;left:-11111px;width:60px;" + + "margin-top:1px;padding:0;border:0"; + div.style.cssText = + "position:relative;display:block;box-sizing:border-box;overflow:scroll;" + + "margin:auto;border:1px;padding:1px;" + + "width:60%;top:1%"; + documentElement.appendChild( container ).appendChild( div ); + + var divStyle = window.getComputedStyle( div ); + pixelPositionVal = divStyle.top !== "1%"; + + // Support: Android 4.0 - 4.3 only, Firefox <=3 - 44 + reliableMarginLeftVal = roundPixelMeasures( divStyle.marginLeft ) === 12; + + // Support: Android 4.0 - 4.3 only, Safari <=9.1 - 10.1, iOS <=7.0 - 9.3 + // Some styles come back with percentage values, even though they shouldn't + div.style.right = "60%"; + pixelBoxStylesVal = roundPixelMeasures( divStyle.right ) === 36; + + // Support: IE 9 - 11 only + // Detect misreporting of content dimensions for box-sizing:border-box elements + boxSizingReliableVal = roundPixelMeasures( divStyle.width ) === 36; + + // Support: IE 9 only + // Detect overflow:scroll screwiness (gh-3699) + // Support: Chrome <=64 + // Don't get tricked when zoom affects offsetWidth (gh-4029) + div.style.position = "absolute"; + scrollboxSizeVal = roundPixelMeasures( div.offsetWidth / 3 ) === 12; + + documentElement.removeChild( container ); + + // Nullify the div so it wouldn't be stored in the memory and + // it will also be a sign that checks already performed + div = null; + } + + function roundPixelMeasures( measure ) { + return Math.round( parseFloat( measure ) ); + } + + var pixelPositionVal, boxSizingReliableVal, scrollboxSizeVal, pixelBoxStylesVal, + reliableTrDimensionsVal, reliableMarginLeftVal, + container = document.createElement( "div" ), + div = document.createElement( "div" ); + + // Finish early in limited (non-browser) environments + if ( !div.style ) { + return; + } + + // Support: IE <=9 - 11 only + // Style of cloned element affects source element cloned (#8908) + div.style.backgroundClip = "content-box"; + div.cloneNode( true ).style.backgroundClip = ""; + support.clearCloneStyle = div.style.backgroundClip === "content-box"; + + jQuery.extend( support, { + boxSizingReliable: function() { + computeStyleTests(); + return boxSizingReliableVal; + }, + pixelBoxStyles: function() { + computeStyleTests(); + return pixelBoxStylesVal; + }, + pixelPosition: function() { + computeStyleTests(); + return pixelPositionVal; + }, + reliableMarginLeft: function() { + computeStyleTests(); + return reliableMarginLeftVal; + }, + scrollboxSize: function() { + computeStyleTests(); + return scrollboxSizeVal; + }, + + // Support: IE 9 - 11+, Edge 15 - 18+ + // IE/Edge misreport `getComputedStyle` of table rows with width/height + // set in CSS while `offset*` properties report correct values. + // Behavior in IE 9 is more subtle than in newer versions & it passes + // some versions of this test; make sure not to make it pass there! + // + // Support: Firefox 70+ + // Only Firefox includes border widths + // in computed dimensions. (gh-4529) + reliableTrDimensions: function() { + var table, tr, trChild, trStyle; + if ( reliableTrDimensionsVal == null ) { + table = document.createElement( "table" ); + tr = document.createElement( "tr" ); + trChild = document.createElement( "div" ); + + table.style.cssText = "position:absolute;left:-11111px;border-collapse:separate"; + tr.style.cssText = "border:1px solid"; + + // Support: Chrome 86+ + // Height set through cssText does not get applied. + // Computed height then comes back as 0. + tr.style.height = "1px"; + trChild.style.height = "9px"; + + // Support: Android 8 Chrome 86+ + // In our bodyBackground.html iframe, + // display for all div elements is set to "inline", + // which causes a problem only in Android 8 Chrome 86. + // Ensuring the div is display: block + // gets around this issue. + trChild.style.display = "block"; + + documentElement + .appendChild( table ) + .appendChild( tr ) + .appendChild( trChild ); + + trStyle = window.getComputedStyle( tr ); + reliableTrDimensionsVal = ( parseInt( trStyle.height, 10 ) + + parseInt( trStyle.borderTopWidth, 10 ) + + parseInt( trStyle.borderBottomWidth, 10 ) ) === tr.offsetHeight; + + documentElement.removeChild( table ); + } + return reliableTrDimensionsVal; + } + } ); +} )(); + + +function curCSS( elem, name, computed ) { + var width, minWidth, maxWidth, ret, + + // Support: Firefox 51+ + // Retrieving style before computed somehow + // fixes an issue with getting wrong values + // on detached elements + style = elem.style; + + computed = computed || getStyles( elem ); + + // getPropertyValue is needed for: + // .css('filter') (IE 9 only, #12537) + // .css('--customProperty) (#3144) + if ( computed ) { + ret = computed.getPropertyValue( name ) || computed[ name ]; + + if ( ret === "" && !isAttached( elem ) ) { + ret = jQuery.style( elem, name ); + } + + // A tribute to the "awesome hack by Dean Edwards" + // Android Browser returns percentage for some values, + // but width seems to be reliably pixels. + // This is against the CSSOM draft spec: + // https://drafts.csswg.org/cssom/#resolved-values + if ( !support.pixelBoxStyles() && rnumnonpx.test( ret ) && rboxStyle.test( name ) ) { + + // Remember the original values + width = style.width; + minWidth = style.minWidth; + maxWidth = style.maxWidth; + + // Put in the new values to get a computed value out + style.minWidth = style.maxWidth = style.width = ret; + ret = computed.width; + + // Revert the changed values + style.width = width; + style.minWidth = minWidth; + style.maxWidth = maxWidth; + } + } + + return ret !== undefined ? + + // Support: IE <=9 - 11 only + // IE returns zIndex value as an integer. + ret + "" : + ret; +} + + +function addGetHookIf( conditionFn, hookFn ) { + + // Define the hook, we'll check on the first run if it's really needed. + return { + get: function() { + if ( conditionFn() ) { + + // Hook not needed (or it's not possible to use it due + // to missing dependency), remove it. + delete this.get; + return; + } + + // Hook needed; redefine it so that the support test is not executed again. + return ( this.get = hookFn ).apply( this, arguments ); + } + }; +} + + +var cssPrefixes = [ "Webkit", "Moz", "ms" ], + emptyStyle = document.createElement( "div" ).style, + vendorProps = {}; + +// Return a vendor-prefixed property or undefined +function vendorPropName( name ) { + + // Check for vendor prefixed names + var capName = name[ 0 ].toUpperCase() + name.slice( 1 ), + i = cssPrefixes.length; + + while ( i-- ) { + name = cssPrefixes[ i ] + capName; + if ( name in emptyStyle ) { + return name; + } + } +} + +// Return a potentially-mapped jQuery.cssProps or vendor prefixed property +function finalPropName( name ) { + var final = jQuery.cssProps[ name ] || vendorProps[ name ]; + + if ( final ) { + return final; + } + if ( name in emptyStyle ) { + return name; + } + return vendorProps[ name ] = vendorPropName( name ) || name; +} + + +var + + // Swappable if display is none or starts with table + // except "table", "table-cell", or "table-caption" + // See here for display values: https://developer.mozilla.org/en-US/docs/CSS/display + rdisplayswap = /^(none|table(?!-c[ea]).+)/, + rcustomProp = /^--/, + cssShow = { position: "absolute", visibility: "hidden", display: "block" }, + cssNormalTransform = { + letterSpacing: "0", + fontWeight: "400" + }; + +function setPositiveNumber( _elem, value, subtract ) { + + // Any relative (+/-) values have already been + // normalized at this point + var matches = rcssNum.exec( value ); + return matches ? + + // Guard against undefined "subtract", e.g., when used as in cssHooks + Math.max( 0, matches[ 2 ] - ( subtract || 0 ) ) + ( matches[ 3 ] || "px" ) : + value; +} + +function boxModelAdjustment( elem, dimension, box, isBorderBox, styles, computedVal ) { + var i = dimension === "width" ? 1 : 0, + extra = 0, + delta = 0; + + // Adjustment may not be necessary + if ( box === ( isBorderBox ? "border" : "content" ) ) { + return 0; + } + + for ( ; i < 4; i += 2 ) { + + // Both box models exclude margin + if ( box === "margin" ) { + delta += jQuery.css( elem, box + cssExpand[ i ], true, styles ); + } + + // If we get here with a content-box, we're seeking "padding" or "border" or "margin" + if ( !isBorderBox ) { + + // Add padding + delta += jQuery.css( elem, "padding" + cssExpand[ i ], true, styles ); + + // For "border" or "margin", add border + if ( box !== "padding" ) { + delta += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); + + // But still keep track of it otherwise + } else { + extra += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); + } + + // If we get here with a border-box (content + padding + border), we're seeking "content" or + // "padding" or "margin" + } else { + + // For "content", subtract padding + if ( box === "content" ) { + delta -= jQuery.css( elem, "padding" + cssExpand[ i ], true, styles ); + } + + // For "content" or "padding", subtract border + if ( box !== "margin" ) { + delta -= jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); + } + } + } + + // Account for positive content-box scroll gutter when requested by providing computedVal + if ( !isBorderBox && computedVal >= 0 ) { + + // offsetWidth/offsetHeight is a rounded sum of content, padding, scroll gutter, and border + // Assuming integer scroll gutter, subtract the rest and round down + delta += Math.max( 0, Math.ceil( + elem[ "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ) ] - + computedVal - + delta - + extra - + 0.5 + + // If offsetWidth/offsetHeight is unknown, then we can't determine content-box scroll gutter + // Use an explicit zero to avoid NaN (gh-3964) + ) ) || 0; + } + + return delta; +} + +function getWidthOrHeight( elem, dimension, extra ) { + + // Start with computed style + var styles = getStyles( elem ), + + // To avoid forcing a reflow, only fetch boxSizing if we need it (gh-4322). + // Fake content-box until we know it's needed to know the true value. + boxSizingNeeded = !support.boxSizingReliable() || extra, + isBorderBox = boxSizingNeeded && + jQuery.css( elem, "boxSizing", false, styles ) === "border-box", + valueIsBorderBox = isBorderBox, + + val = curCSS( elem, dimension, styles ), + offsetProp = "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ); + + // Support: Firefox <=54 + // Return a confounding non-pixel value or feign ignorance, as appropriate. + if ( rnumnonpx.test( val ) ) { + if ( !extra ) { + return val; + } + val = "auto"; + } + + + // Support: IE 9 - 11 only + // Use offsetWidth/offsetHeight for when box sizing is unreliable. + // In those cases, the computed value can be trusted to be border-box. + if ( ( !support.boxSizingReliable() && isBorderBox || + + // Support: IE 10 - 11+, Edge 15 - 18+ + // IE/Edge misreport `getComputedStyle` of table rows with width/height + // set in CSS while `offset*` properties report correct values. + // Interestingly, in some cases IE 9 doesn't suffer from this issue. + !support.reliableTrDimensions() && nodeName( elem, "tr" ) || + + // Fall back to offsetWidth/offsetHeight when value is "auto" + // This happens for inline elements with no explicit setting (gh-3571) + val === "auto" || + + // Support: Android <=4.1 - 4.3 only + // Also use offsetWidth/offsetHeight for misreported inline dimensions (gh-3602) + !parseFloat( val ) && jQuery.css( elem, "display", false, styles ) === "inline" ) && + + // Make sure the element is visible & connected + elem.getClientRects().length ) { + + isBorderBox = jQuery.css( elem, "boxSizing", false, styles ) === "border-box"; + + // Where available, offsetWidth/offsetHeight approximate border box dimensions. + // Where not available (e.g., SVG), assume unreliable box-sizing and interpret the + // retrieved value as a content box dimension. + valueIsBorderBox = offsetProp in elem; + if ( valueIsBorderBox ) { + val = elem[ offsetProp ]; + } + } + + // Normalize "" and auto + val = parseFloat( val ) || 0; + + // Adjust for the element's box model + return ( val + + boxModelAdjustment( + elem, + dimension, + extra || ( isBorderBox ? "border" : "content" ), + valueIsBorderBox, + styles, + + // Provide the current computed size to request scroll gutter calculation (gh-3589) + val + ) + ) + "px"; +} + +jQuery.extend( { + + // Add in style property hooks for overriding the default + // behavior of getting and setting a style property + cssHooks: { + opacity: { + get: function( elem, computed ) { + if ( computed ) { + + // We should always get a number back from opacity + var ret = curCSS( elem, "opacity" ); + return ret === "" ? "1" : ret; + } + } + } + }, + + // Don't automatically add "px" to these possibly-unitless properties + cssNumber: { + "animationIterationCount": true, + "columnCount": true, + "fillOpacity": true, + "flexGrow": true, + "flexShrink": true, + "fontWeight": true, + "gridArea": true, + "gridColumn": true, + "gridColumnEnd": true, + "gridColumnStart": true, + "gridRow": true, + "gridRowEnd": true, + "gridRowStart": true, + "lineHeight": true, + "opacity": true, + "order": true, + "orphans": true, + "widows": true, + "zIndex": true, + "zoom": true + }, + + // Add in properties whose names you wish to fix before + // setting or getting the value + cssProps: {}, + + // Get and set the style property on a DOM Node + style: function( elem, name, value, extra ) { + + // Don't set styles on text and comment nodes + if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) { + return; + } + + // Make sure that we're working with the right name + var ret, type, hooks, + origName = camelCase( name ), + isCustomProp = rcustomProp.test( name ), + style = elem.style; + + // Make sure that we're working with the right name. We don't + // want to query the value if it is a CSS custom property + // since they are user-defined. + if ( !isCustomProp ) { + name = finalPropName( origName ); + } + + // Gets hook for the prefixed version, then unprefixed version + hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ]; + + // Check if we're setting a value + if ( value !== undefined ) { + type = typeof value; + + // Convert "+=" or "-=" to relative numbers (#7345) + if ( type === "string" && ( ret = rcssNum.exec( value ) ) && ret[ 1 ] ) { + value = adjustCSS( elem, name, ret ); + + // Fixes bug #9237 + type = "number"; + } + + // Make sure that null and NaN values aren't set (#7116) + if ( value == null || value !== value ) { + return; + } + + // If a number was passed in, add the unit (except for certain CSS properties) + // The isCustomProp check can be removed in jQuery 4.0 when we only auto-append + // "px" to a few hardcoded values. + if ( type === "number" && !isCustomProp ) { + value += ret && ret[ 3 ] || ( jQuery.cssNumber[ origName ] ? "" : "px" ); + } + + // background-* props affect original clone's values + if ( !support.clearCloneStyle && value === "" && name.indexOf( "background" ) === 0 ) { + style[ name ] = "inherit"; + } + + // If a hook was provided, use that value, otherwise just set the specified value + if ( !hooks || !( "set" in hooks ) || + ( value = hooks.set( elem, value, extra ) ) !== undefined ) { + + if ( isCustomProp ) { + style.setProperty( name, value ); + } else { + style[ name ] = value; + } + } + + } else { + + // If a hook was provided get the non-computed value from there + if ( hooks && "get" in hooks && + ( ret = hooks.get( elem, false, extra ) ) !== undefined ) { + + return ret; + } + + // Otherwise just get the value from the style object + return style[ name ]; + } + }, + + css: function( elem, name, extra, styles ) { + var val, num, hooks, + origName = camelCase( name ), + isCustomProp = rcustomProp.test( name ); + + // Make sure that we're working with the right name. We don't + // want to modify the value if it is a CSS custom property + // since they are user-defined. + if ( !isCustomProp ) { + name = finalPropName( origName ); + } + + // Try prefixed name followed by the unprefixed name + hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ]; + + // If a hook was provided get the computed value from there + if ( hooks && "get" in hooks ) { + val = hooks.get( elem, true, extra ); + } + + // Otherwise, if a way to get the computed value exists, use that + if ( val === undefined ) { + val = curCSS( elem, name, styles ); + } + + // Convert "normal" to computed value + if ( val === "normal" && name in cssNormalTransform ) { + val = cssNormalTransform[ name ]; + } + + // Make numeric if forced or a qualifier was provided and val looks numeric + if ( extra === "" || extra ) { + num = parseFloat( val ); + return extra === true || isFinite( num ) ? num || 0 : val; + } + + return val; + } +} ); + +jQuery.each( [ "height", "width" ], function( _i, dimension ) { + jQuery.cssHooks[ dimension ] = { + get: function( elem, computed, extra ) { + if ( computed ) { + + // Certain elements can have dimension info if we invisibly show them + // but it must have a current display style that would benefit + return rdisplayswap.test( jQuery.css( elem, "display" ) ) && + + // Support: Safari 8+ + // Table columns in Safari have non-zero offsetWidth & zero + // getBoundingClientRect().width unless display is changed. + // Support: IE <=11 only + // Running getBoundingClientRect on a disconnected node + // in IE throws an error. + ( !elem.getClientRects().length || !elem.getBoundingClientRect().width ) ? + swap( elem, cssShow, function() { + return getWidthOrHeight( elem, dimension, extra ); + } ) : + getWidthOrHeight( elem, dimension, extra ); + } + }, + + set: function( elem, value, extra ) { + var matches, + styles = getStyles( elem ), + + // Only read styles.position if the test has a chance to fail + // to avoid forcing a reflow. + scrollboxSizeBuggy = !support.scrollboxSize() && + styles.position === "absolute", + + // To avoid forcing a reflow, only fetch boxSizing if we need it (gh-3991) + boxSizingNeeded = scrollboxSizeBuggy || extra, + isBorderBox = boxSizingNeeded && + jQuery.css( elem, "boxSizing", false, styles ) === "border-box", + subtract = extra ? + boxModelAdjustment( + elem, + dimension, + extra, + isBorderBox, + styles + ) : + 0; + + // Account for unreliable border-box dimensions by comparing offset* to computed and + // faking a content-box to get border and padding (gh-3699) + if ( isBorderBox && scrollboxSizeBuggy ) { + subtract -= Math.ceil( + elem[ "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ) ] - + parseFloat( styles[ dimension ] ) - + boxModelAdjustment( elem, dimension, "border", false, styles ) - + 0.5 + ); + } + + // Convert to pixels if value adjustment is needed + if ( subtract && ( matches = rcssNum.exec( value ) ) && + ( matches[ 3 ] || "px" ) !== "px" ) { + + elem.style[ dimension ] = value; + value = jQuery.css( elem, dimension ); + } + + return setPositiveNumber( elem, value, subtract ); + } + }; +} ); + +jQuery.cssHooks.marginLeft = addGetHookIf( support.reliableMarginLeft, + function( elem, computed ) { + if ( computed ) { + return ( parseFloat( curCSS( elem, "marginLeft" ) ) || + elem.getBoundingClientRect().left - + swap( elem, { marginLeft: 0 }, function() { + return elem.getBoundingClientRect().left; + } ) + ) + "px"; + } + } +); + +// These hooks are used by animate to expand properties +jQuery.each( { + margin: "", + padding: "", + border: "Width" +}, function( prefix, suffix ) { + jQuery.cssHooks[ prefix + suffix ] = { + expand: function( value ) { + var i = 0, + expanded = {}, + + // Assumes a single number if not a string + parts = typeof value === "string" ? value.split( " " ) : [ value ]; + + for ( ; i < 4; i++ ) { + expanded[ prefix + cssExpand[ i ] + suffix ] = + parts[ i ] || parts[ i - 2 ] || parts[ 0 ]; + } + + return expanded; + } + }; + + if ( prefix !== "margin" ) { + jQuery.cssHooks[ prefix + suffix ].set = setPositiveNumber; + } +} ); + +jQuery.fn.extend( { + css: function( name, value ) { + return access( this, function( elem, name, value ) { + var styles, len, + map = {}, + i = 0; + + if ( Array.isArray( name ) ) { + styles = getStyles( elem ); + len = name.length; + + for ( ; i < len; i++ ) { + map[ name[ i ] ] = jQuery.css( elem, name[ i ], false, styles ); + } + + return map; + } + + return value !== undefined ? + jQuery.style( elem, name, value ) : + jQuery.css( elem, name ); + }, name, value, arguments.length > 1 ); + } +} ); + + +function Tween( elem, options, prop, end, easing ) { + return new Tween.prototype.init( elem, options, prop, end, easing ); +} +jQuery.Tween = Tween; + +Tween.prototype = { + constructor: Tween, + init: function( elem, options, prop, end, easing, unit ) { + this.elem = elem; + this.prop = prop; + this.easing = easing || jQuery.easing._default; + this.options = options; + this.start = this.now = this.cur(); + this.end = end; + this.unit = unit || ( jQuery.cssNumber[ prop ] ? "" : "px" ); + }, + cur: function() { + var hooks = Tween.propHooks[ this.prop ]; + + return hooks && hooks.get ? + hooks.get( this ) : + Tween.propHooks._default.get( this ); + }, + run: function( percent ) { + var eased, + hooks = Tween.propHooks[ this.prop ]; + + if ( this.options.duration ) { + this.pos = eased = jQuery.easing[ this.easing ]( + percent, this.options.duration * percent, 0, 1, this.options.duration + ); + } else { + this.pos = eased = percent; + } + this.now = ( this.end - this.start ) * eased + this.start; + + if ( this.options.step ) { + this.options.step.call( this.elem, this.now, this ); + } + + if ( hooks && hooks.set ) { + hooks.set( this ); + } else { + Tween.propHooks._default.set( this ); + } + return this; + } +}; + +Tween.prototype.init.prototype = Tween.prototype; + +Tween.propHooks = { + _default: { + get: function( tween ) { + var result; + + // Use a property on the element directly when it is not a DOM element, + // or when there is no matching style property that exists. + if ( tween.elem.nodeType !== 1 || + tween.elem[ tween.prop ] != null && tween.elem.style[ tween.prop ] == null ) { + return tween.elem[ tween.prop ]; + } + + // Passing an empty string as a 3rd parameter to .css will automatically + // attempt a parseFloat and fallback to a string if the parse fails. + // Simple values such as "10px" are parsed to Float; + // complex values such as "rotate(1rad)" are returned as-is. + result = jQuery.css( tween.elem, tween.prop, "" ); + + // Empty strings, null, undefined and "auto" are converted to 0. + return !result || result === "auto" ? 0 : result; + }, + set: function( tween ) { + + // Use step hook for back compat. + // Use cssHook if its there. + // Use .style if available and use plain properties where available. + if ( jQuery.fx.step[ tween.prop ] ) { + jQuery.fx.step[ tween.prop ]( tween ); + } else if ( tween.elem.nodeType === 1 && ( + jQuery.cssHooks[ tween.prop ] || + tween.elem.style[ finalPropName( tween.prop ) ] != null ) ) { + jQuery.style( tween.elem, tween.prop, tween.now + tween.unit ); + } else { + tween.elem[ tween.prop ] = tween.now; + } + } + } +}; + +// Support: IE <=9 only +// Panic based approach to setting things on disconnected nodes +Tween.propHooks.scrollTop = Tween.propHooks.scrollLeft = { + set: function( tween ) { + if ( tween.elem.nodeType && tween.elem.parentNode ) { + tween.elem[ tween.prop ] = tween.now; + } + } +}; + +jQuery.easing = { + linear: function( p ) { + return p; + }, + swing: function( p ) { + return 0.5 - Math.cos( p * Math.PI ) / 2; + }, + _default: "swing" +}; + +jQuery.fx = Tween.prototype.init; + +// Back compat <1.8 extension point +jQuery.fx.step = {}; + + + + +var + fxNow, inProgress, + rfxtypes = /^(?:toggle|show|hide)$/, + rrun = /queueHooks$/; + +function schedule() { + if ( inProgress ) { + if ( document.hidden === false && window.requestAnimationFrame ) { + window.requestAnimationFrame( schedule ); + } else { + window.setTimeout( schedule, jQuery.fx.interval ); + } + + jQuery.fx.tick(); + } +} + +// Animations created synchronously will run synchronously +function createFxNow() { + window.setTimeout( function() { + fxNow = undefined; + } ); + return ( fxNow = Date.now() ); +} + +// Generate parameters to create a standard animation +function genFx( type, includeWidth ) { + var which, + i = 0, + attrs = { height: type }; + + // If we include width, step value is 1 to do all cssExpand values, + // otherwise step value is 2 to skip over Left and Right + includeWidth = includeWidth ? 1 : 0; + for ( ; i < 4; i += 2 - includeWidth ) { + which = cssExpand[ i ]; + attrs[ "margin" + which ] = attrs[ "padding" + which ] = type; + } + + if ( includeWidth ) { + attrs.opacity = attrs.width = type; + } + + return attrs; +} + +function createTween( value, prop, animation ) { + var tween, + collection = ( Animation.tweeners[ prop ] || [] ).concat( Animation.tweeners[ "*" ] ), + index = 0, + length = collection.length; + for ( ; index < length; index++ ) { + if ( ( tween = collection[ index ].call( animation, prop, value ) ) ) { + + // We're done with this property + return tween; + } + } +} + +function defaultPrefilter( elem, props, opts ) { + var prop, value, toggle, hooks, oldfire, propTween, restoreDisplay, display, + isBox = "width" in props || "height" in props, + anim = this, + orig = {}, + style = elem.style, + hidden = elem.nodeType && isHiddenWithinTree( elem ), + dataShow = dataPriv.get( elem, "fxshow" ); + + // Queue-skipping animations hijack the fx hooks + if ( !opts.queue ) { + hooks = jQuery._queueHooks( elem, "fx" ); + if ( hooks.unqueued == null ) { + hooks.unqueued = 0; + oldfire = hooks.empty.fire; + hooks.empty.fire = function() { + if ( !hooks.unqueued ) { + oldfire(); + } + }; + } + hooks.unqueued++; + + anim.always( function() { + + // Ensure the complete handler is called before this completes + anim.always( function() { + hooks.unqueued--; + if ( !jQuery.queue( elem, "fx" ).length ) { + hooks.empty.fire(); + } + } ); + } ); + } + + // Detect show/hide animations + for ( prop in props ) { + value = props[ prop ]; + if ( rfxtypes.test( value ) ) { + delete props[ prop ]; + toggle = toggle || value === "toggle"; + if ( value === ( hidden ? "hide" : "show" ) ) { + + // Pretend to be hidden if this is a "show" and + // there is still data from a stopped show/hide + if ( value === "show" && dataShow && dataShow[ prop ] !== undefined ) { + hidden = true; + + // Ignore all other no-op show/hide data + } else { + continue; + } + } + orig[ prop ] = dataShow && dataShow[ prop ] || jQuery.style( elem, prop ); + } + } + + // Bail out if this is a no-op like .hide().hide() + propTween = !jQuery.isEmptyObject( props ); + if ( !propTween && jQuery.isEmptyObject( orig ) ) { + return; + } + + // Restrict "overflow" and "display" styles during box animations + if ( isBox && elem.nodeType === 1 ) { + + // Support: IE <=9 - 11, Edge 12 - 15 + // Record all 3 overflow attributes because IE does not infer the shorthand + // from identically-valued overflowX and overflowY and Edge just mirrors + // the overflowX value there. + opts.overflow = [ style.overflow, style.overflowX, style.overflowY ]; + + // Identify a display type, preferring old show/hide data over the CSS cascade + restoreDisplay = dataShow && dataShow.display; + if ( restoreDisplay == null ) { + restoreDisplay = dataPriv.get( elem, "display" ); + } + display = jQuery.css( elem, "display" ); + if ( display === "none" ) { + if ( restoreDisplay ) { + display = restoreDisplay; + } else { + + // Get nonempty value(s) by temporarily forcing visibility + showHide( [ elem ], true ); + restoreDisplay = elem.style.display || restoreDisplay; + display = jQuery.css( elem, "display" ); + showHide( [ elem ] ); + } + } + + // Animate inline elements as inline-block + if ( display === "inline" || display === "inline-block" && restoreDisplay != null ) { + if ( jQuery.css( elem, "float" ) === "none" ) { + + // Restore the original display value at the end of pure show/hide animations + if ( !propTween ) { + anim.done( function() { + style.display = restoreDisplay; + } ); + if ( restoreDisplay == null ) { + display = style.display; + restoreDisplay = display === "none" ? "" : display; + } + } + style.display = "inline-block"; + } + } + } + + if ( opts.overflow ) { + style.overflow = "hidden"; + anim.always( function() { + style.overflow = opts.overflow[ 0 ]; + style.overflowX = opts.overflow[ 1 ]; + style.overflowY = opts.overflow[ 2 ]; + } ); + } + + // Implement show/hide animations + propTween = false; + for ( prop in orig ) { + + // General show/hide setup for this element animation + if ( !propTween ) { + if ( dataShow ) { + if ( "hidden" in dataShow ) { + hidden = dataShow.hidden; + } + } else { + dataShow = dataPriv.access( elem, "fxshow", { display: restoreDisplay } ); + } + + // Store hidden/visible for toggle so `.stop().toggle()` "reverses" + if ( toggle ) { + dataShow.hidden = !hidden; + } + + // Show elements before animating them + if ( hidden ) { + showHide( [ elem ], true ); + } + + /* eslint-disable no-loop-func */ + + anim.done( function() { + + /* eslint-enable no-loop-func */ + + // The final step of a "hide" animation is actually hiding the element + if ( !hidden ) { + showHide( [ elem ] ); + } + dataPriv.remove( elem, "fxshow" ); + for ( prop in orig ) { + jQuery.style( elem, prop, orig[ prop ] ); + } + } ); + } + + // Per-property setup + propTween = createTween( hidden ? dataShow[ prop ] : 0, prop, anim ); + if ( !( prop in dataShow ) ) { + dataShow[ prop ] = propTween.start; + if ( hidden ) { + propTween.end = propTween.start; + propTween.start = 0; + } + } + } +} + +function propFilter( props, specialEasing ) { + var index, name, easing, value, hooks; + + // camelCase, specialEasing and expand cssHook pass + for ( index in props ) { + name = camelCase( index ); + easing = specialEasing[ name ]; + value = props[ index ]; + if ( Array.isArray( value ) ) { + easing = value[ 1 ]; + value = props[ index ] = value[ 0 ]; + } + + if ( index !== name ) { + props[ name ] = value; + delete props[ index ]; + } + + hooks = jQuery.cssHooks[ name ]; + if ( hooks && "expand" in hooks ) { + value = hooks.expand( value ); + delete props[ name ]; + + // Not quite $.extend, this won't overwrite existing keys. + // Reusing 'index' because we have the correct "name" + for ( index in value ) { + if ( !( index in props ) ) { + props[ index ] = value[ index ]; + specialEasing[ index ] = easing; + } + } + } else { + specialEasing[ name ] = easing; + } + } +} + +function Animation( elem, properties, options ) { + var result, + stopped, + index = 0, + length = Animation.prefilters.length, + deferred = jQuery.Deferred().always( function() { + + // Don't match elem in the :animated selector + delete tick.elem; + } ), + tick = function() { + if ( stopped ) { + return false; + } + var currentTime = fxNow || createFxNow(), + remaining = Math.max( 0, animation.startTime + animation.duration - currentTime ), + + // Support: Android 2.3 only + // Archaic crash bug won't allow us to use `1 - ( 0.5 || 0 )` (#12497) + temp = remaining / animation.duration || 0, + percent = 1 - temp, + index = 0, + length = animation.tweens.length; + + for ( ; index < length; index++ ) { + animation.tweens[ index ].run( percent ); + } + + deferred.notifyWith( elem, [ animation, percent, remaining ] ); + + // If there's more to do, yield + if ( percent < 1 && length ) { + return remaining; + } + + // If this was an empty animation, synthesize a final progress notification + if ( !length ) { + deferred.notifyWith( elem, [ animation, 1, 0 ] ); + } + + // Resolve the animation and report its conclusion + deferred.resolveWith( elem, [ animation ] ); + return false; + }, + animation = deferred.promise( { + elem: elem, + props: jQuery.extend( {}, properties ), + opts: jQuery.extend( true, { + specialEasing: {}, + easing: jQuery.easing._default + }, options ), + originalProperties: properties, + originalOptions: options, + startTime: fxNow || createFxNow(), + duration: options.duration, + tweens: [], + createTween: function( prop, end ) { + var tween = jQuery.Tween( elem, animation.opts, prop, end, + animation.opts.specialEasing[ prop ] || animation.opts.easing ); + animation.tweens.push( tween ); + return tween; + }, + stop: function( gotoEnd ) { + var index = 0, + + // If we are going to the end, we want to run all the tweens + // otherwise we skip this part + length = gotoEnd ? animation.tweens.length : 0; + if ( stopped ) { + return this; + } + stopped = true; + for ( ; index < length; index++ ) { + animation.tweens[ index ].run( 1 ); + } + + // Resolve when we played the last frame; otherwise, reject + if ( gotoEnd ) { + deferred.notifyWith( elem, [ animation, 1, 0 ] ); + deferred.resolveWith( elem, [ animation, gotoEnd ] ); + } else { + deferred.rejectWith( elem, [ animation, gotoEnd ] ); + } + return this; + } + } ), + props = animation.props; + + propFilter( props, animation.opts.specialEasing ); + + for ( ; index < length; index++ ) { + result = Animation.prefilters[ index ].call( animation, elem, props, animation.opts ); + if ( result ) { + if ( isFunction( result.stop ) ) { + jQuery._queueHooks( animation.elem, animation.opts.queue ).stop = + result.stop.bind( result ); + } + return result; + } + } + + jQuery.map( props, createTween, animation ); + + if ( isFunction( animation.opts.start ) ) { + animation.opts.start.call( elem, animation ); + } + + // Attach callbacks from options + animation + .progress( animation.opts.progress ) + .done( animation.opts.done, animation.opts.complete ) + .fail( animation.opts.fail ) + .always( animation.opts.always ); + + jQuery.fx.timer( + jQuery.extend( tick, { + elem: elem, + anim: animation, + queue: animation.opts.queue + } ) + ); + + return animation; +} + +jQuery.Animation = jQuery.extend( Animation, { + + tweeners: { + "*": [ function( prop, value ) { + var tween = this.createTween( prop, value ); + adjustCSS( tween.elem, prop, rcssNum.exec( value ), tween ); + return tween; + } ] + }, + + tweener: function( props, callback ) { + if ( isFunction( props ) ) { + callback = props; + props = [ "*" ]; + } else { + props = props.match( rnothtmlwhite ); + } + + var prop, + index = 0, + length = props.length; + + for ( ; index < length; index++ ) { + prop = props[ index ]; + Animation.tweeners[ prop ] = Animation.tweeners[ prop ] || []; + Animation.tweeners[ prop ].unshift( callback ); + } + }, + + prefilters: [ defaultPrefilter ], + + prefilter: function( callback, prepend ) { + if ( prepend ) { + Animation.prefilters.unshift( callback ); + } else { + Animation.prefilters.push( callback ); + } + } +} ); + +jQuery.speed = function( speed, easing, fn ) { + var opt = speed && typeof speed === "object" ? jQuery.extend( {}, speed ) : { + complete: fn || !fn && easing || + isFunction( speed ) && speed, + duration: speed, + easing: fn && easing || easing && !isFunction( easing ) && easing + }; + + // Go to the end state if fx are off + if ( jQuery.fx.off ) { + opt.duration = 0; + + } else { + if ( typeof opt.duration !== "number" ) { + if ( opt.duration in jQuery.fx.speeds ) { + opt.duration = jQuery.fx.speeds[ opt.duration ]; + + } else { + opt.duration = jQuery.fx.speeds._default; + } + } + } + + // Normalize opt.queue - true/undefined/null -> "fx" + if ( opt.queue == null || opt.queue === true ) { + opt.queue = "fx"; + } + + // Queueing + opt.old = opt.complete; + + opt.complete = function() { + if ( isFunction( opt.old ) ) { + opt.old.call( this ); + } + + if ( opt.queue ) { + jQuery.dequeue( this, opt.queue ); + } + }; + + return opt; +}; + +jQuery.fn.extend( { + fadeTo: function( speed, to, easing, callback ) { + + // Show any hidden elements after setting opacity to 0 + return this.filter( isHiddenWithinTree ).css( "opacity", 0 ).show() + + // Animate to the value specified + .end().animate( { opacity: to }, speed, easing, callback ); + }, + animate: function( prop, speed, easing, callback ) { + var empty = jQuery.isEmptyObject( prop ), + optall = jQuery.speed( speed, easing, callback ), + doAnimation = function() { + + // Operate on a copy of prop so per-property easing won't be lost + var anim = Animation( this, jQuery.extend( {}, prop ), optall ); + + // Empty animations, or finishing resolves immediately + if ( empty || dataPriv.get( this, "finish" ) ) { + anim.stop( true ); + } + }; + + doAnimation.finish = doAnimation; + + return empty || optall.queue === false ? + this.each( doAnimation ) : + this.queue( optall.queue, doAnimation ); + }, + stop: function( type, clearQueue, gotoEnd ) { + var stopQueue = function( hooks ) { + var stop = hooks.stop; + delete hooks.stop; + stop( gotoEnd ); + }; + + if ( typeof type !== "string" ) { + gotoEnd = clearQueue; + clearQueue = type; + type = undefined; + } + if ( clearQueue ) { + this.queue( type || "fx", [] ); + } + + return this.each( function() { + var dequeue = true, + index = type != null && type + "queueHooks", + timers = jQuery.timers, + data = dataPriv.get( this ); + + if ( index ) { + if ( data[ index ] && data[ index ].stop ) { + stopQueue( data[ index ] ); + } + } else { + for ( index in data ) { + if ( data[ index ] && data[ index ].stop && rrun.test( index ) ) { + stopQueue( data[ index ] ); + } + } + } + + for ( index = timers.length; index--; ) { + if ( timers[ index ].elem === this && + ( type == null || timers[ index ].queue === type ) ) { + + timers[ index ].anim.stop( gotoEnd ); + dequeue = false; + timers.splice( index, 1 ); + } + } + + // Start the next in the queue if the last step wasn't forced. + // Timers currently will call their complete callbacks, which + // will dequeue but only if they were gotoEnd. + if ( dequeue || !gotoEnd ) { + jQuery.dequeue( this, type ); + } + } ); + }, + finish: function( type ) { + if ( type !== false ) { + type = type || "fx"; + } + return this.each( function() { + var index, + data = dataPriv.get( this ), + queue = data[ type + "queue" ], + hooks = data[ type + "queueHooks" ], + timers = jQuery.timers, + length = queue ? queue.length : 0; + + // Enable finishing flag on private data + data.finish = true; + + // Empty the queue first + jQuery.queue( this, type, [] ); + + if ( hooks && hooks.stop ) { + hooks.stop.call( this, true ); + } + + // Look for any active animations, and finish them + for ( index = timers.length; index--; ) { + if ( timers[ index ].elem === this && timers[ index ].queue === type ) { + timers[ index ].anim.stop( true ); + timers.splice( index, 1 ); + } + } + + // Look for any animations in the old queue and finish them + for ( index = 0; index < length; index++ ) { + if ( queue[ index ] && queue[ index ].finish ) { + queue[ index ].finish.call( this ); + } + } + + // Turn off finishing flag + delete data.finish; + } ); + } +} ); + +jQuery.each( [ "toggle", "show", "hide" ], function( _i, name ) { + var cssFn = jQuery.fn[ name ]; + jQuery.fn[ name ] = function( speed, easing, callback ) { + return speed == null || typeof speed === "boolean" ? + cssFn.apply( this, arguments ) : + this.animate( genFx( name, true ), speed, easing, callback ); + }; +} ); + +// Generate shortcuts for custom animations +jQuery.each( { + slideDown: genFx( "show" ), + slideUp: genFx( "hide" ), + slideToggle: genFx( "toggle" ), + fadeIn: { opacity: "show" }, + fadeOut: { opacity: "hide" }, + fadeToggle: { opacity: "toggle" } +}, function( name, props ) { + jQuery.fn[ name ] = function( speed, easing, callback ) { + return this.animate( props, speed, easing, callback ); + }; +} ); + +jQuery.timers = []; +jQuery.fx.tick = function() { + var timer, + i = 0, + timers = jQuery.timers; + + fxNow = Date.now(); + + for ( ; i < timers.length; i++ ) { + timer = timers[ i ]; + + // Run the timer and safely remove it when done (allowing for external removal) + if ( !timer() && timers[ i ] === timer ) { + timers.splice( i--, 1 ); + } + } + + if ( !timers.length ) { + jQuery.fx.stop(); + } + fxNow = undefined; +}; + +jQuery.fx.timer = function( timer ) { + jQuery.timers.push( timer ); + jQuery.fx.start(); +}; + +jQuery.fx.interval = 13; +jQuery.fx.start = function() { + if ( inProgress ) { + return; + } + + inProgress = true; + schedule(); +}; + +jQuery.fx.stop = function() { + inProgress = null; +}; + +jQuery.fx.speeds = { + slow: 600, + fast: 200, + + // Default speed + _default: 400 +}; + + +// Based off of the plugin by Clint Helfers, with permission. +// https://web.archive.org/web/20100324014747/http://blindsignals.com/index.php/2009/07/jquery-delay/ +jQuery.fn.delay = function( time, type ) { + time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time; + type = type || "fx"; + + return this.queue( type, function( next, hooks ) { + var timeout = window.setTimeout( next, time ); + hooks.stop = function() { + window.clearTimeout( timeout ); + }; + } ); +}; + + +( function() { + var input = document.createElement( "input" ), + select = document.createElement( "select" ), + opt = select.appendChild( document.createElement( "option" ) ); + + input.type = "checkbox"; + + // Support: Android <=4.3 only + // Default value for a checkbox should be "on" + support.checkOn = input.value !== ""; + + // Support: IE <=11 only + // Must access selectedIndex to make default options select + support.optSelected = opt.selected; + + // Support: IE <=11 only + // An input loses its value after becoming a radio + input = document.createElement( "input" ); + input.value = "t"; + input.type = "radio"; + support.radioValue = input.value === "t"; +} )(); + + +var boolHook, + attrHandle = jQuery.expr.attrHandle; + +jQuery.fn.extend( { + attr: function( name, value ) { + return access( this, jQuery.attr, name, value, arguments.length > 1 ); + }, + + removeAttr: function( name ) { + return this.each( function() { + jQuery.removeAttr( this, name ); + } ); + } +} ); + +jQuery.extend( { + attr: function( elem, name, value ) { + var ret, hooks, + nType = elem.nodeType; + + // Don't get/set attributes on text, comment and attribute nodes + if ( nType === 3 || nType === 8 || nType === 2 ) { + return; + } + + // Fallback to prop when attributes are not supported + if ( typeof elem.getAttribute === "undefined" ) { + return jQuery.prop( elem, name, value ); + } + + // Attribute hooks are determined by the lowercase version + // Grab necessary hook if one is defined + if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) { + hooks = jQuery.attrHooks[ name.toLowerCase() ] || + ( jQuery.expr.match.bool.test( name ) ? boolHook : undefined ); + } + + if ( value !== undefined ) { + if ( value === null ) { + jQuery.removeAttr( elem, name ); + return; + } + + if ( hooks && "set" in hooks && + ( ret = hooks.set( elem, value, name ) ) !== undefined ) { + return ret; + } + + elem.setAttribute( name, value + "" ); + return value; + } + + if ( hooks && "get" in hooks && ( ret = hooks.get( elem, name ) ) !== null ) { + return ret; + } + + ret = jQuery.find.attr( elem, name ); + + // Non-existent attributes return null, we normalize to undefined + return ret == null ? undefined : ret; + }, + + attrHooks: { + type: { + set: function( elem, value ) { + if ( !support.radioValue && value === "radio" && + nodeName( elem, "input" ) ) { + var val = elem.value; + elem.setAttribute( "type", value ); + if ( val ) { + elem.value = val; + } + return value; + } + } + } + }, + + removeAttr: function( elem, value ) { + var name, + i = 0, + + // Attribute names can contain non-HTML whitespace characters + // https://html.spec.whatwg.org/multipage/syntax.html#attributes-2 + attrNames = value && value.match( rnothtmlwhite ); + + if ( attrNames && elem.nodeType === 1 ) { + while ( ( name = attrNames[ i++ ] ) ) { + elem.removeAttribute( name ); + } + } + } +} ); + +// Hooks for boolean attributes +boolHook = { + set: function( elem, value, name ) { + if ( value === false ) { + + // Remove boolean attributes when set to false + jQuery.removeAttr( elem, name ); + } else { + elem.setAttribute( name, name ); + } + return name; + } +}; + +jQuery.each( jQuery.expr.match.bool.source.match( /\w+/g ), function( _i, name ) { + var getter = attrHandle[ name ] || jQuery.find.attr; + + attrHandle[ name ] = function( elem, name, isXML ) { + var ret, handle, + lowercaseName = name.toLowerCase(); + + if ( !isXML ) { + + // Avoid an infinite loop by temporarily removing this function from the getter + handle = attrHandle[ lowercaseName ]; + attrHandle[ lowercaseName ] = ret; + ret = getter( elem, name, isXML ) != null ? + lowercaseName : + null; + attrHandle[ lowercaseName ] = handle; + } + return ret; + }; +} ); + + + + +var rfocusable = /^(?:input|select|textarea|button)$/i, + rclickable = /^(?:a|area)$/i; + +jQuery.fn.extend( { + prop: function( name, value ) { + return access( this, jQuery.prop, name, value, arguments.length > 1 ); + }, + + removeProp: function( name ) { + return this.each( function() { + delete this[ jQuery.propFix[ name ] || name ]; + } ); + } +} ); + +jQuery.extend( { + prop: function( elem, name, value ) { + var ret, hooks, + nType = elem.nodeType; + + // Don't get/set properties on text, comment and attribute nodes + if ( nType === 3 || nType === 8 || nType === 2 ) { + return; + } + + if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) { + + // Fix name and attach hooks + name = jQuery.propFix[ name ] || name; + hooks = jQuery.propHooks[ name ]; + } + + if ( value !== undefined ) { + if ( hooks && "set" in hooks && + ( ret = hooks.set( elem, value, name ) ) !== undefined ) { + return ret; + } + + return ( elem[ name ] = value ); + } + + if ( hooks && "get" in hooks && ( ret = hooks.get( elem, name ) ) !== null ) { + return ret; + } + + return elem[ name ]; + }, + + propHooks: { + tabIndex: { + get: function( elem ) { + + // Support: IE <=9 - 11 only + // elem.tabIndex doesn't always return the + // correct value when it hasn't been explicitly set + // https://web.archive.org/web/20141116233347/http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/ + // Use proper attribute retrieval(#12072) + var tabindex = jQuery.find.attr( elem, "tabindex" ); + + if ( tabindex ) { + return parseInt( tabindex, 10 ); + } + + if ( + rfocusable.test( elem.nodeName ) || + rclickable.test( elem.nodeName ) && + elem.href + ) { + return 0; + } + + return -1; + } + } + }, + + propFix: { + "for": "htmlFor", + "class": "className" + } +} ); + +// Support: IE <=11 only +// Accessing the selectedIndex property +// forces the browser to respect setting selected +// on the option +// The getter ensures a default option is selected +// when in an optgroup +// eslint rule "no-unused-expressions" is disabled for this code +// since it considers such accessions noop +if ( !support.optSelected ) { + jQuery.propHooks.selected = { + get: function( elem ) { + + /* eslint no-unused-expressions: "off" */ + + var parent = elem.parentNode; + if ( parent && parent.parentNode ) { + parent.parentNode.selectedIndex; + } + return null; + }, + set: function( elem ) { + + /* eslint no-unused-expressions: "off" */ + + var parent = elem.parentNode; + if ( parent ) { + parent.selectedIndex; + + if ( parent.parentNode ) { + parent.parentNode.selectedIndex; + } + } + } + }; +} + +jQuery.each( [ + "tabIndex", + "readOnly", + "maxLength", + "cellSpacing", + "cellPadding", + "rowSpan", + "colSpan", + "useMap", + "frameBorder", + "contentEditable" +], function() { + jQuery.propFix[ this.toLowerCase() ] = this; +} ); + + + + + // Strip and collapse whitespace according to HTML spec + // https://infra.spec.whatwg.org/#strip-and-collapse-ascii-whitespace + function stripAndCollapse( value ) { + var tokens = value.match( rnothtmlwhite ) || []; + return tokens.join( " " ); + } + + +function getClass( elem ) { + return elem.getAttribute && elem.getAttribute( "class" ) || ""; +} + +function classesToArray( value ) { + if ( Array.isArray( value ) ) { + return value; + } + if ( typeof value === "string" ) { + return value.match( rnothtmlwhite ) || []; + } + return []; +} + +jQuery.fn.extend( { + addClass: function( value ) { + var classes, elem, cur, curValue, clazz, j, finalValue, + i = 0; + + if ( isFunction( value ) ) { + return this.each( function( j ) { + jQuery( this ).addClass( value.call( this, j, getClass( this ) ) ); + } ); + } + + classes = classesToArray( value ); + + if ( classes.length ) { + while ( ( elem = this[ i++ ] ) ) { + curValue = getClass( elem ); + cur = elem.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " ); + + if ( cur ) { + j = 0; + while ( ( clazz = classes[ j++ ] ) ) { + if ( cur.indexOf( " " + clazz + " " ) < 0 ) { + cur += clazz + " "; + } + } + + // Only assign if different to avoid unneeded rendering. + finalValue = stripAndCollapse( cur ); + if ( curValue !== finalValue ) { + elem.setAttribute( "class", finalValue ); + } + } + } + } + + return this; + }, + + removeClass: function( value ) { + var classes, elem, cur, curValue, clazz, j, finalValue, + i = 0; + + if ( isFunction( value ) ) { + return this.each( function( j ) { + jQuery( this ).removeClass( value.call( this, j, getClass( this ) ) ); + } ); + } + + if ( !arguments.length ) { + return this.attr( "class", "" ); + } + + classes = classesToArray( value ); + + if ( classes.length ) { + while ( ( elem = this[ i++ ] ) ) { + curValue = getClass( elem ); + + // This expression is here for better compressibility (see addClass) + cur = elem.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " ); + + if ( cur ) { + j = 0; + while ( ( clazz = classes[ j++ ] ) ) { + + // Remove *all* instances + while ( cur.indexOf( " " + clazz + " " ) > -1 ) { + cur = cur.replace( " " + clazz + " ", " " ); + } + } + + // Only assign if different to avoid unneeded rendering. + finalValue = stripAndCollapse( cur ); + if ( curValue !== finalValue ) { + elem.setAttribute( "class", finalValue ); + } + } + } + } + + return this; + }, + + toggleClass: function( value, stateVal ) { + var type = typeof value, + isValidValue = type === "string" || Array.isArray( value ); + + if ( typeof stateVal === "boolean" && isValidValue ) { + return stateVal ? this.addClass( value ) : this.removeClass( value ); + } + + if ( isFunction( value ) ) { + return this.each( function( i ) { + jQuery( this ).toggleClass( + value.call( this, i, getClass( this ), stateVal ), + stateVal + ); + } ); + } + + return this.each( function() { + var className, i, self, classNames; + + if ( isValidValue ) { + + // Toggle individual class names + i = 0; + self = jQuery( this ); + classNames = classesToArray( value ); + + while ( ( className = classNames[ i++ ] ) ) { + + // Check each className given, space separated list + if ( self.hasClass( className ) ) { + self.removeClass( className ); + } else { + self.addClass( className ); + } + } + + // Toggle whole class name + } else if ( value === undefined || type === "boolean" ) { + className = getClass( this ); + if ( className ) { + + // Store className if set + dataPriv.set( this, "__className__", className ); + } + + // If the element has a class name or if we're passed `false`, + // then remove the whole classname (if there was one, the above saved it). + // Otherwise bring back whatever was previously saved (if anything), + // falling back to the empty string if nothing was stored. + if ( this.setAttribute ) { + this.setAttribute( "class", + className || value === false ? + "" : + dataPriv.get( this, "__className__" ) || "" + ); + } + } + } ); + }, + + hasClass: function( selector ) { + var className, elem, + i = 0; + + className = " " + selector + " "; + while ( ( elem = this[ i++ ] ) ) { + if ( elem.nodeType === 1 && + ( " " + stripAndCollapse( getClass( elem ) ) + " " ).indexOf( className ) > -1 ) { + return true; + } + } + + return false; + } +} ); + + + + +var rreturn = /\r/g; + +jQuery.fn.extend( { + val: function( value ) { + var hooks, ret, valueIsFunction, + elem = this[ 0 ]; + + if ( !arguments.length ) { + if ( elem ) { + hooks = jQuery.valHooks[ elem.type ] || + jQuery.valHooks[ elem.nodeName.toLowerCase() ]; + + if ( hooks && + "get" in hooks && + ( ret = hooks.get( elem, "value" ) ) !== undefined + ) { + return ret; + } + + ret = elem.value; + + // Handle most common string cases + if ( typeof ret === "string" ) { + return ret.replace( rreturn, "" ); + } + + // Handle cases where value is null/undef or number + return ret == null ? "" : ret; + } + + return; + } + + valueIsFunction = isFunction( value ); + + return this.each( function( i ) { + var val; + + if ( this.nodeType !== 1 ) { + return; + } + + if ( valueIsFunction ) { + val = value.call( this, i, jQuery( this ).val() ); + } else { + val = value; + } + + // Treat null/undefined as ""; convert numbers to string + if ( val == null ) { + val = ""; + + } else if ( typeof val === "number" ) { + val += ""; + + } else if ( Array.isArray( val ) ) { + val = jQuery.map( val, function( value ) { + return value == null ? "" : value + ""; + } ); + } + + hooks = jQuery.valHooks[ this.type ] || jQuery.valHooks[ this.nodeName.toLowerCase() ]; + + // If set returns undefined, fall back to normal setting + if ( !hooks || !( "set" in hooks ) || hooks.set( this, val, "value" ) === undefined ) { + this.value = val; + } + } ); + } +} ); + +jQuery.extend( { + valHooks: { + option: { + get: function( elem ) { + + var val = jQuery.find.attr( elem, "value" ); + return val != null ? + val : + + // Support: IE <=10 - 11 only + // option.text throws exceptions (#14686, #14858) + // Strip and collapse whitespace + // https://html.spec.whatwg.org/#strip-and-collapse-whitespace + stripAndCollapse( jQuery.text( elem ) ); + } + }, + select: { + get: function( elem ) { + var value, option, i, + options = elem.options, + index = elem.selectedIndex, + one = elem.type === "select-one", + values = one ? null : [], + max = one ? index + 1 : options.length; + + if ( index < 0 ) { + i = max; + + } else { + i = one ? index : 0; + } + + // Loop through all the selected options + for ( ; i < max; i++ ) { + option = options[ i ]; + + // Support: IE <=9 only + // IE8-9 doesn't update selected after form reset (#2551) + if ( ( option.selected || i === index ) && + + // Don't return options that are disabled or in a disabled optgroup + !option.disabled && + ( !option.parentNode.disabled || + !nodeName( option.parentNode, "optgroup" ) ) ) { + + // Get the specific value for the option + value = jQuery( option ).val(); + + // We don't need an array for one selects + if ( one ) { + return value; + } + + // Multi-Selects return an array + values.push( value ); + } + } + + return values; + }, + + set: function( elem, value ) { + var optionSet, option, + options = elem.options, + values = jQuery.makeArray( value ), + i = options.length; + + while ( i-- ) { + option = options[ i ]; + + /* eslint-disable no-cond-assign */ + + if ( option.selected = + jQuery.inArray( jQuery.valHooks.option.get( option ), values ) > -1 + ) { + optionSet = true; + } + + /* eslint-enable no-cond-assign */ + } + + // Force browsers to behave consistently when non-matching value is set + if ( !optionSet ) { + elem.selectedIndex = -1; + } + return values; + } + } + } +} ); + +// Radios and checkboxes getter/setter +jQuery.each( [ "radio", "checkbox" ], function() { + jQuery.valHooks[ this ] = { + set: function( elem, value ) { + if ( Array.isArray( value ) ) { + return ( elem.checked = jQuery.inArray( jQuery( elem ).val(), value ) > -1 ); + } + } + }; + if ( !support.checkOn ) { + jQuery.valHooks[ this ].get = function( elem ) { + return elem.getAttribute( "value" ) === null ? "on" : elem.value; + }; + } +} ); + + + + +// Return jQuery for attributes-only inclusion + + +support.focusin = "onfocusin" in window; + + +var rfocusMorph = /^(?:focusinfocus|focusoutblur)$/, + stopPropagationCallback = function( e ) { + e.stopPropagation(); + }; + +jQuery.extend( jQuery.event, { + + trigger: function( event, data, elem, onlyHandlers ) { + + var i, cur, tmp, bubbleType, ontype, handle, special, lastElement, + eventPath = [ elem || document ], + type = hasOwn.call( event, "type" ) ? event.type : event, + namespaces = hasOwn.call( event, "namespace" ) ? event.namespace.split( "." ) : []; + + cur = lastElement = tmp = elem = elem || document; + + // Don't do events on text and comment nodes + if ( elem.nodeType === 3 || elem.nodeType === 8 ) { + return; + } + + // focus/blur morphs to focusin/out; ensure we're not firing them right now + if ( rfocusMorph.test( type + jQuery.event.triggered ) ) { + return; + } + + if ( type.indexOf( "." ) > -1 ) { + + // Namespaced trigger; create a regexp to match event type in handle() + namespaces = type.split( "." ); + type = namespaces.shift(); + namespaces.sort(); + } + ontype = type.indexOf( ":" ) < 0 && "on" + type; + + // Caller can pass in a jQuery.Event object, Object, or just an event type string + event = event[ jQuery.expando ] ? + event : + new jQuery.Event( type, typeof event === "object" && event ); + + // Trigger bitmask: & 1 for native handlers; & 2 for jQuery (always true) + event.isTrigger = onlyHandlers ? 2 : 3; + event.namespace = namespaces.join( "." ); + event.rnamespace = event.namespace ? + new RegExp( "(^|\\.)" + namespaces.join( "\\.(?:.*\\.|)" ) + "(\\.|$)" ) : + null; + + // Clean up the event in case it is being reused + event.result = undefined; + if ( !event.target ) { + event.target = elem; + } + + // Clone any incoming data and prepend the event, creating the handler arg list + data = data == null ? + [ event ] : + jQuery.makeArray( data, [ event ] ); + + // Allow special events to draw outside the lines + special = jQuery.event.special[ type ] || {}; + if ( !onlyHandlers && special.trigger && special.trigger.apply( elem, data ) === false ) { + return; + } + + // Determine event propagation path in advance, per W3C events spec (#9951) + // Bubble up to document, then to window; watch for a global ownerDocument var (#9724) + if ( !onlyHandlers && !special.noBubble && !isWindow( elem ) ) { + + bubbleType = special.delegateType || type; + if ( !rfocusMorph.test( bubbleType + type ) ) { + cur = cur.parentNode; + } + for ( ; cur; cur = cur.parentNode ) { + eventPath.push( cur ); + tmp = cur; + } + + // Only add window if we got to document (e.g., not plain obj or detached DOM) + if ( tmp === ( elem.ownerDocument || document ) ) { + eventPath.push( tmp.defaultView || tmp.parentWindow || window ); + } + } + + // Fire handlers on the event path + i = 0; + while ( ( cur = eventPath[ i++ ] ) && !event.isPropagationStopped() ) { + lastElement = cur; + event.type = i > 1 ? + bubbleType : + special.bindType || type; + + // jQuery handler + handle = ( dataPriv.get( cur, "events" ) || Object.create( null ) )[ event.type ] && + dataPriv.get( cur, "handle" ); + if ( handle ) { + handle.apply( cur, data ); + } + + // Native handler + handle = ontype && cur[ ontype ]; + if ( handle && handle.apply && acceptData( cur ) ) { + event.result = handle.apply( cur, data ); + if ( event.result === false ) { + event.preventDefault(); + } + } + } + event.type = type; + + // If nobody prevented the default action, do it now + if ( !onlyHandlers && !event.isDefaultPrevented() ) { + + if ( ( !special._default || + special._default.apply( eventPath.pop(), data ) === false ) && + acceptData( elem ) ) { + + // Call a native DOM method on the target with the same name as the event. + // Don't do default actions on window, that's where global variables be (#6170) + if ( ontype && isFunction( elem[ type ] ) && !isWindow( elem ) ) { + + // Don't re-trigger an onFOO event when we call its FOO() method + tmp = elem[ ontype ]; + + if ( tmp ) { + elem[ ontype ] = null; + } + + // Prevent re-triggering of the same event, since we already bubbled it above + jQuery.event.triggered = type; + + if ( event.isPropagationStopped() ) { + lastElement.addEventListener( type, stopPropagationCallback ); + } + + elem[ type ](); + + if ( event.isPropagationStopped() ) { + lastElement.removeEventListener( type, stopPropagationCallback ); + } + + jQuery.event.triggered = undefined; + + if ( tmp ) { + elem[ ontype ] = tmp; + } + } + } + } + + return event.result; + }, + + // Piggyback on a donor event to simulate a different one + // Used only for `focus(in | out)` events + simulate: function( type, elem, event ) { + var e = jQuery.extend( + new jQuery.Event(), + event, + { + type: type, + isSimulated: true + } + ); + + jQuery.event.trigger( e, null, elem ); + } + +} ); + +jQuery.fn.extend( { + + trigger: function( type, data ) { + return this.each( function() { + jQuery.event.trigger( type, data, this ); + } ); + }, + triggerHandler: function( type, data ) { + var elem = this[ 0 ]; + if ( elem ) { + return jQuery.event.trigger( type, data, elem, true ); + } + } +} ); + + +// Support: Firefox <=44 +// Firefox doesn't have focus(in | out) events +// Related ticket - https://bugzilla.mozilla.org/show_bug.cgi?id=687787 +// +// Support: Chrome <=48 - 49, Safari <=9.0 - 9.1 +// focus(in | out) events fire after focus & blur events, +// which is spec violation - http://www.w3.org/TR/DOM-Level-3-Events/#events-focusevent-event-order +// Related ticket - https://bugs.chromium.org/p/chromium/issues/detail?id=449857 +if ( !support.focusin ) { + jQuery.each( { focus: "focusin", blur: "focusout" }, function( orig, fix ) { + + // Attach a single capturing handler on the document while someone wants focusin/focusout + var handler = function( event ) { + jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ) ); + }; + + jQuery.event.special[ fix ] = { + setup: function() { + + // Handle: regular nodes (via `this.ownerDocument`), window + // (via `this.document`) & document (via `this`). + var doc = this.ownerDocument || this.document || this, + attaches = dataPriv.access( doc, fix ); + + if ( !attaches ) { + doc.addEventListener( orig, handler, true ); + } + dataPriv.access( doc, fix, ( attaches || 0 ) + 1 ); + }, + teardown: function() { + var doc = this.ownerDocument || this.document || this, + attaches = dataPriv.access( doc, fix ) - 1; + + if ( !attaches ) { + doc.removeEventListener( orig, handler, true ); + dataPriv.remove( doc, fix ); + + } else { + dataPriv.access( doc, fix, attaches ); + } + } + }; + } ); +} +var location = window.location; + +var nonce = { guid: Date.now() }; + +var rquery = ( /\?/ ); + + + +// Cross-browser xml parsing +jQuery.parseXML = function( data ) { + var xml, parserErrorElem; + if ( !data || typeof data !== "string" ) { + return null; + } + + // Support: IE 9 - 11 only + // IE throws on parseFromString with invalid input. + try { + xml = ( new window.DOMParser() ).parseFromString( data, "text/xml" ); + } catch ( e ) {} + + parserErrorElem = xml && xml.getElementsByTagName( "parsererror" )[ 0 ]; + if ( !xml || parserErrorElem ) { + jQuery.error( "Invalid XML: " + ( + parserErrorElem ? + jQuery.map( parserErrorElem.childNodes, function( el ) { + return el.textContent; + } ).join( "\n" ) : + data + ) ); + } + return xml; +}; + + +var + rbracket = /\[\]$/, + rCRLF = /\r?\n/g, + rsubmitterTypes = /^(?:submit|button|image|reset|file)$/i, + rsubmittable = /^(?:input|select|textarea|keygen)/i; + +function buildParams( prefix, obj, traditional, add ) { + var name; + + if ( Array.isArray( obj ) ) { + + // Serialize array item. + jQuery.each( obj, function( i, v ) { + if ( traditional || rbracket.test( prefix ) ) { + + // Treat each array item as a scalar. + add( prefix, v ); + + } else { + + // Item is non-scalar (array or object), encode its numeric index. + buildParams( + prefix + "[" + ( typeof v === "object" && v != null ? i : "" ) + "]", + v, + traditional, + add + ); + } + } ); + + } else if ( !traditional && toType( obj ) === "object" ) { + + // Serialize object item. + for ( name in obj ) { + buildParams( prefix + "[" + name + "]", obj[ name ], traditional, add ); + } + + } else { + + // Serialize scalar item. + add( prefix, obj ); + } +} + +// Serialize an array of form elements or a set of +// key/values into a query string +jQuery.param = function( a, traditional ) { + var prefix, + s = [], + add = function( key, valueOrFunction ) { + + // If value is a function, invoke it and use its return value + var value = isFunction( valueOrFunction ) ? + valueOrFunction() : + valueOrFunction; + + s[ s.length ] = encodeURIComponent( key ) + "=" + + encodeURIComponent( value == null ? "" : value ); + }; + + if ( a == null ) { + return ""; + } + + // If an array was passed in, assume that it is an array of form elements. + if ( Array.isArray( a ) || ( a.jquery && !jQuery.isPlainObject( a ) ) ) { + + // Serialize the form elements + jQuery.each( a, function() { + add( this.name, this.value ); + } ); + + } else { + + // If traditional, encode the "old" way (the way 1.3.2 or older + // did it), otherwise encode params recursively. + for ( prefix in a ) { + buildParams( prefix, a[ prefix ], traditional, add ); + } + } + + // Return the resulting serialization + return s.join( "&" ); +}; + +jQuery.fn.extend( { + serialize: function() { + return jQuery.param( this.serializeArray() ); + }, + serializeArray: function() { + return this.map( function() { + + // Can add propHook for "elements" to filter or add form elements + var elements = jQuery.prop( this, "elements" ); + return elements ? jQuery.makeArray( elements ) : this; + } ).filter( function() { + var type = this.type; + + // Use .is( ":disabled" ) so that fieldset[disabled] works + return this.name && !jQuery( this ).is( ":disabled" ) && + rsubmittable.test( this.nodeName ) && !rsubmitterTypes.test( type ) && + ( this.checked || !rcheckableType.test( type ) ); + } ).map( function( _i, elem ) { + var val = jQuery( this ).val(); + + if ( val == null ) { + return null; + } + + if ( Array.isArray( val ) ) { + return jQuery.map( val, function( val ) { + return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) }; + } ); + } + + return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) }; + } ).get(); + } +} ); + + +var + r20 = /%20/g, + rhash = /#.*$/, + rantiCache = /([?&])_=[^&]*/, + rheaders = /^(.*?):[ \t]*([^\r\n]*)$/mg, + + // #7653, #8125, #8152: local protocol detection + rlocalProtocol = /^(?:about|app|app-storage|.+-extension|file|res|widget):$/, + rnoContent = /^(?:GET|HEAD)$/, + rprotocol = /^\/\//, + + /* Prefilters + * 1) They are useful to introduce custom dataTypes (see ajax/jsonp.js for an example) + * 2) These are called: + * - BEFORE asking for a transport + * - AFTER param serialization (s.data is a string if s.processData is true) + * 3) key is the dataType + * 4) the catchall symbol "*" can be used + * 5) execution will start with transport dataType and THEN continue down to "*" if needed + */ + prefilters = {}, + + /* Transports bindings + * 1) key is the dataType + * 2) the catchall symbol "*" can be used + * 3) selection will start with transport dataType and THEN go to "*" if needed + */ + transports = {}, + + // Avoid comment-prolog char sequence (#10098); must appease lint and evade compression + allTypes = "*/".concat( "*" ), + + // Anchor tag for parsing the document origin + originAnchor = document.createElement( "a" ); + +originAnchor.href = location.href; + +// Base "constructor" for jQuery.ajaxPrefilter and jQuery.ajaxTransport +function addToPrefiltersOrTransports( structure ) { + + // dataTypeExpression is optional and defaults to "*" + return function( dataTypeExpression, func ) { + + if ( typeof dataTypeExpression !== "string" ) { + func = dataTypeExpression; + dataTypeExpression = "*"; + } + + var dataType, + i = 0, + dataTypes = dataTypeExpression.toLowerCase().match( rnothtmlwhite ) || []; + + if ( isFunction( func ) ) { + + // For each dataType in the dataTypeExpression + while ( ( dataType = dataTypes[ i++ ] ) ) { + + // Prepend if requested + if ( dataType[ 0 ] === "+" ) { + dataType = dataType.slice( 1 ) || "*"; + ( structure[ dataType ] = structure[ dataType ] || [] ).unshift( func ); + + // Otherwise append + } else { + ( structure[ dataType ] = structure[ dataType ] || [] ).push( func ); + } + } + } + }; +} + +// Base inspection function for prefilters and transports +function inspectPrefiltersOrTransports( structure, options, originalOptions, jqXHR ) { + + var inspected = {}, + seekingTransport = ( structure === transports ); + + function inspect( dataType ) { + var selected; + inspected[ dataType ] = true; + jQuery.each( structure[ dataType ] || [], function( _, prefilterOrFactory ) { + var dataTypeOrTransport = prefilterOrFactory( options, originalOptions, jqXHR ); + if ( typeof dataTypeOrTransport === "string" && + !seekingTransport && !inspected[ dataTypeOrTransport ] ) { + + options.dataTypes.unshift( dataTypeOrTransport ); + inspect( dataTypeOrTransport ); + return false; + } else if ( seekingTransport ) { + return !( selected = dataTypeOrTransport ); + } + } ); + return selected; + } + + return inspect( options.dataTypes[ 0 ] ) || !inspected[ "*" ] && inspect( "*" ); +} + +// A special extend for ajax options +// that takes "flat" options (not to be deep extended) +// Fixes #9887 +function ajaxExtend( target, src ) { + var key, deep, + flatOptions = jQuery.ajaxSettings.flatOptions || {}; + + for ( key in src ) { + if ( src[ key ] !== undefined ) { + ( flatOptions[ key ] ? target : ( deep || ( deep = {} ) ) )[ key ] = src[ key ]; + } + } + if ( deep ) { + jQuery.extend( true, target, deep ); + } + + return target; +} + +/* Handles responses to an ajax request: + * - finds the right dataType (mediates between content-type and expected dataType) + * - returns the corresponding response + */ +function ajaxHandleResponses( s, jqXHR, responses ) { + + var ct, type, finalDataType, firstDataType, + contents = s.contents, + dataTypes = s.dataTypes; + + // Remove auto dataType and get content-type in the process + while ( dataTypes[ 0 ] === "*" ) { + dataTypes.shift(); + if ( ct === undefined ) { + ct = s.mimeType || jqXHR.getResponseHeader( "Content-Type" ); + } + } + + // Check if we're dealing with a known content-type + if ( ct ) { + for ( type in contents ) { + if ( contents[ type ] && contents[ type ].test( ct ) ) { + dataTypes.unshift( type ); + break; + } + } + } + + // Check to see if we have a response for the expected dataType + if ( dataTypes[ 0 ] in responses ) { + finalDataType = dataTypes[ 0 ]; + } else { + + // Try convertible dataTypes + for ( type in responses ) { + if ( !dataTypes[ 0 ] || s.converters[ type + " " + dataTypes[ 0 ] ] ) { + finalDataType = type; + break; + } + if ( !firstDataType ) { + firstDataType = type; + } + } + + // Or just use first one + finalDataType = finalDataType || firstDataType; + } + + // If we found a dataType + // We add the dataType to the list if needed + // and return the corresponding response + if ( finalDataType ) { + if ( finalDataType !== dataTypes[ 0 ] ) { + dataTypes.unshift( finalDataType ); + } + return responses[ finalDataType ]; + } +} + +/* Chain conversions given the request and the original response + * Also sets the responseXXX fields on the jqXHR instance + */ +function ajaxConvert( s, response, jqXHR, isSuccess ) { + var conv2, current, conv, tmp, prev, + converters = {}, + + // Work with a copy of dataTypes in case we need to modify it for conversion + dataTypes = s.dataTypes.slice(); + + // Create converters map with lowercased keys + if ( dataTypes[ 1 ] ) { + for ( conv in s.converters ) { + converters[ conv.toLowerCase() ] = s.converters[ conv ]; + } + } + + current = dataTypes.shift(); + + // Convert to each sequential dataType + while ( current ) { + + if ( s.responseFields[ current ] ) { + jqXHR[ s.responseFields[ current ] ] = response; + } + + // Apply the dataFilter if provided + if ( !prev && isSuccess && s.dataFilter ) { + response = s.dataFilter( response, s.dataType ); + } + + prev = current; + current = dataTypes.shift(); + + if ( current ) { + + // There's only work to do if current dataType is non-auto + if ( current === "*" ) { + + current = prev; + + // Convert response if prev dataType is non-auto and differs from current + } else if ( prev !== "*" && prev !== current ) { + + // Seek a direct converter + conv = converters[ prev + " " + current ] || converters[ "* " + current ]; + + // If none found, seek a pair + if ( !conv ) { + for ( conv2 in converters ) { + + // If conv2 outputs current + tmp = conv2.split( " " ); + if ( tmp[ 1 ] === current ) { + + // If prev can be converted to accepted input + conv = converters[ prev + " " + tmp[ 0 ] ] || + converters[ "* " + tmp[ 0 ] ]; + if ( conv ) { + + // Condense equivalence converters + if ( conv === true ) { + conv = converters[ conv2 ]; + + // Otherwise, insert the intermediate dataType + } else if ( converters[ conv2 ] !== true ) { + current = tmp[ 0 ]; + dataTypes.unshift( tmp[ 1 ] ); + } + break; + } + } + } + } + + // Apply converter (if not an equivalence) + if ( conv !== true ) { + + // Unless errors are allowed to bubble, catch and return them + if ( conv && s.throws ) { + response = conv( response ); + } else { + try { + response = conv( response ); + } catch ( e ) { + return { + state: "parsererror", + error: conv ? e : "No conversion from " + prev + " to " + current + }; + } + } + } + } + } + } + + return { state: "success", data: response }; +} + +jQuery.extend( { + + // Counter for holding the number of active queries + active: 0, + + // Last-Modified header cache for next request + lastModified: {}, + etag: {}, + + ajaxSettings: { + url: location.href, + type: "GET", + isLocal: rlocalProtocol.test( location.protocol ), + global: true, + processData: true, + async: true, + contentType: "application/x-www-form-urlencoded; charset=UTF-8", + + /* + timeout: 0, + data: null, + dataType: null, + username: null, + password: null, + cache: null, + throws: false, + traditional: false, + headers: {}, + */ + + accepts: { + "*": allTypes, + text: "text/plain", + html: "text/html", + xml: "application/xml, text/xml", + json: "application/json, text/javascript" + }, + + contents: { + xml: /\bxml\b/, + html: /\bhtml/, + json: /\bjson\b/ + }, + + responseFields: { + xml: "responseXML", + text: "responseText", + json: "responseJSON" + }, + + // Data converters + // Keys separate source (or catchall "*") and destination types with a single space + converters: { + + // Convert anything to text + "* text": String, + + // Text to html (true = no transformation) + "text html": true, + + // Evaluate text as a json expression + "text json": JSON.parse, + + // Parse text as xml + "text xml": jQuery.parseXML + }, + + // For options that shouldn't be deep extended: + // you can add your own custom options here if + // and when you create one that shouldn't be + // deep extended (see ajaxExtend) + flatOptions: { + url: true, + context: true + } + }, + + // Creates a full fledged settings object into target + // with both ajaxSettings and settings fields. + // If target is omitted, writes into ajaxSettings. + ajaxSetup: function( target, settings ) { + return settings ? + + // Building a settings object + ajaxExtend( ajaxExtend( target, jQuery.ajaxSettings ), settings ) : + + // Extending ajaxSettings + ajaxExtend( jQuery.ajaxSettings, target ); + }, + + ajaxPrefilter: addToPrefiltersOrTransports( prefilters ), + ajaxTransport: addToPrefiltersOrTransports( transports ), + + // Main method + ajax: function( url, options ) { + + // If url is an object, simulate pre-1.5 signature + if ( typeof url === "object" ) { + options = url; + url = undefined; + } + + // Force options to be an object + options = options || {}; + + var transport, + + // URL without anti-cache param + cacheURL, + + // Response headers + responseHeadersString, + responseHeaders, + + // timeout handle + timeoutTimer, + + // Url cleanup var + urlAnchor, + + // Request state (becomes false upon send and true upon completion) + completed, + + // To know if global events are to be dispatched + fireGlobals, + + // Loop variable + i, + + // uncached part of the url + uncached, + + // Create the final options object + s = jQuery.ajaxSetup( {}, options ), + + // Callbacks context + callbackContext = s.context || s, + + // Context for global events is callbackContext if it is a DOM node or jQuery collection + globalEventContext = s.context && + ( callbackContext.nodeType || callbackContext.jquery ) ? + jQuery( callbackContext ) : + jQuery.event, + + // Deferreds + deferred = jQuery.Deferred(), + completeDeferred = jQuery.Callbacks( "once memory" ), + + // Status-dependent callbacks + statusCode = s.statusCode || {}, + + // Headers (they are sent all at once) + requestHeaders = {}, + requestHeadersNames = {}, + + // Default abort message + strAbort = "canceled", + + // Fake xhr + jqXHR = { + readyState: 0, + + // Builds headers hashtable if needed + getResponseHeader: function( key ) { + var match; + if ( completed ) { + if ( !responseHeaders ) { + responseHeaders = {}; + while ( ( match = rheaders.exec( responseHeadersString ) ) ) { + responseHeaders[ match[ 1 ].toLowerCase() + " " ] = + ( responseHeaders[ match[ 1 ].toLowerCase() + " " ] || [] ) + .concat( match[ 2 ] ); + } + } + match = responseHeaders[ key.toLowerCase() + " " ]; + } + return match == null ? null : match.join( ", " ); + }, + + // Raw string + getAllResponseHeaders: function() { + return completed ? responseHeadersString : null; + }, + + // Caches the header + setRequestHeader: function( name, value ) { + if ( completed == null ) { + name = requestHeadersNames[ name.toLowerCase() ] = + requestHeadersNames[ name.toLowerCase() ] || name; + requestHeaders[ name ] = value; + } + return this; + }, + + // Overrides response content-type header + overrideMimeType: function( type ) { + if ( completed == null ) { + s.mimeType = type; + } + return this; + }, + + // Status-dependent callbacks + statusCode: function( map ) { + var code; + if ( map ) { + if ( completed ) { + + // Execute the appropriate callbacks + jqXHR.always( map[ jqXHR.status ] ); + } else { + + // Lazy-add the new callbacks in a way that preserves old ones + for ( code in map ) { + statusCode[ code ] = [ statusCode[ code ], map[ code ] ]; + } + } + } + return this; + }, + + // Cancel the request + abort: function( statusText ) { + var finalText = statusText || strAbort; + if ( transport ) { + transport.abort( finalText ); + } + done( 0, finalText ); + return this; + } + }; + + // Attach deferreds + deferred.promise( jqXHR ); + + // Add protocol if not provided (prefilters might expect it) + // Handle falsy url in the settings object (#10093: consistency with old signature) + // We also use the url parameter if available + s.url = ( ( url || s.url || location.href ) + "" ) + .replace( rprotocol, location.protocol + "//" ); + + // Alias method option to type as per ticket #12004 + s.type = options.method || options.type || s.method || s.type; + + // Extract dataTypes list + s.dataTypes = ( s.dataType || "*" ).toLowerCase().match( rnothtmlwhite ) || [ "" ]; + + // A cross-domain request is in order when the origin doesn't match the current origin. + if ( s.crossDomain == null ) { + urlAnchor = document.createElement( "a" ); + + // Support: IE <=8 - 11, Edge 12 - 15 + // IE throws exception on accessing the href property if url is malformed, + // e.g. http://example.com:80x/ + try { + urlAnchor.href = s.url; + + // Support: IE <=8 - 11 only + // Anchor's host property isn't correctly set when s.url is relative + urlAnchor.href = urlAnchor.href; + s.crossDomain = originAnchor.protocol + "//" + originAnchor.host !== + urlAnchor.protocol + "//" + urlAnchor.host; + } catch ( e ) { + + // If there is an error parsing the URL, assume it is crossDomain, + // it can be rejected by the transport if it is invalid + s.crossDomain = true; + } + } + + // Convert data if not already a string + if ( s.data && s.processData && typeof s.data !== "string" ) { + s.data = jQuery.param( s.data, s.traditional ); + } + + // Apply prefilters + inspectPrefiltersOrTransports( prefilters, s, options, jqXHR ); + + // If request was aborted inside a prefilter, stop there + if ( completed ) { + return jqXHR; + } + + // We can fire global events as of now if asked to + // Don't fire events if jQuery.event is undefined in an AMD-usage scenario (#15118) + fireGlobals = jQuery.event && s.global; + + // Watch for a new set of requests + if ( fireGlobals && jQuery.active++ === 0 ) { + jQuery.event.trigger( "ajaxStart" ); + } + + // Uppercase the type + s.type = s.type.toUpperCase(); + + // Determine if request has content + s.hasContent = !rnoContent.test( s.type ); + + // Save the URL in case we're toying with the If-Modified-Since + // and/or If-None-Match header later on + // Remove hash to simplify url manipulation + cacheURL = s.url.replace( rhash, "" ); + + // More options handling for requests with no content + if ( !s.hasContent ) { + + // Remember the hash so we can put it back + uncached = s.url.slice( cacheURL.length ); + + // If data is available and should be processed, append data to url + if ( s.data && ( s.processData || typeof s.data === "string" ) ) { + cacheURL += ( rquery.test( cacheURL ) ? "&" : "?" ) + s.data; + + // #9682: remove data so that it's not used in an eventual retry + delete s.data; + } + + // Add or update anti-cache param if needed + if ( s.cache === false ) { + cacheURL = cacheURL.replace( rantiCache, "$1" ); + uncached = ( rquery.test( cacheURL ) ? "&" : "?" ) + "_=" + ( nonce.guid++ ) + + uncached; + } + + // Put hash and anti-cache on the URL that will be requested (gh-1732) + s.url = cacheURL + uncached; + + // Change '%20' to '+' if this is encoded form body content (gh-2658) + } else if ( s.data && s.processData && + ( s.contentType || "" ).indexOf( "application/x-www-form-urlencoded" ) === 0 ) { + s.data = s.data.replace( r20, "+" ); + } + + // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. + if ( s.ifModified ) { + if ( jQuery.lastModified[ cacheURL ] ) { + jqXHR.setRequestHeader( "If-Modified-Since", jQuery.lastModified[ cacheURL ] ); + } + if ( jQuery.etag[ cacheURL ] ) { + jqXHR.setRequestHeader( "If-None-Match", jQuery.etag[ cacheURL ] ); + } + } + + // Set the correct header, if data is being sent + if ( s.data && s.hasContent && s.contentType !== false || options.contentType ) { + jqXHR.setRequestHeader( "Content-Type", s.contentType ); + } + + // Set the Accepts header for the server, depending on the dataType + jqXHR.setRequestHeader( + "Accept", + s.dataTypes[ 0 ] && s.accepts[ s.dataTypes[ 0 ] ] ? + s.accepts[ s.dataTypes[ 0 ] ] + + ( s.dataTypes[ 0 ] !== "*" ? ", " + allTypes + "; q=0.01" : "" ) : + s.accepts[ "*" ] + ); + + // Check for headers option + for ( i in s.headers ) { + jqXHR.setRequestHeader( i, s.headers[ i ] ); + } + + // Allow custom headers/mimetypes and early abort + if ( s.beforeSend && + ( s.beforeSend.call( callbackContext, jqXHR, s ) === false || completed ) ) { + + // Abort if not done already and return + return jqXHR.abort(); + } + + // Aborting is no longer a cancellation + strAbort = "abort"; + + // Install callbacks on deferreds + completeDeferred.add( s.complete ); + jqXHR.done( s.success ); + jqXHR.fail( s.error ); + + // Get transport + transport = inspectPrefiltersOrTransports( transports, s, options, jqXHR ); + + // If no transport, we auto-abort + if ( !transport ) { + done( -1, "No Transport" ); + } else { + jqXHR.readyState = 1; + + // Send global event + if ( fireGlobals ) { + globalEventContext.trigger( "ajaxSend", [ jqXHR, s ] ); + } + + // If request was aborted inside ajaxSend, stop there + if ( completed ) { + return jqXHR; + } + + // Timeout + if ( s.async && s.timeout > 0 ) { + timeoutTimer = window.setTimeout( function() { + jqXHR.abort( "timeout" ); + }, s.timeout ); + } + + try { + completed = false; + transport.send( requestHeaders, done ); + } catch ( e ) { + + // Rethrow post-completion exceptions + if ( completed ) { + throw e; + } + + // Propagate others as results + done( -1, e ); + } + } + + // Callback for when everything is done + function done( status, nativeStatusText, responses, headers ) { + var isSuccess, success, error, response, modified, + statusText = nativeStatusText; + + // Ignore repeat invocations + if ( completed ) { + return; + } + + completed = true; + + // Clear timeout if it exists + if ( timeoutTimer ) { + window.clearTimeout( timeoutTimer ); + } + + // Dereference transport for early garbage collection + // (no matter how long the jqXHR object will be used) + transport = undefined; + + // Cache response headers + responseHeadersString = headers || ""; + + // Set readyState + jqXHR.readyState = status > 0 ? 4 : 0; + + // Determine if successful + isSuccess = status >= 200 && status < 300 || status === 304; + + // Get response data + if ( responses ) { + response = ajaxHandleResponses( s, jqXHR, responses ); + } + + // Use a noop converter for missing script but not if jsonp + if ( !isSuccess && + jQuery.inArray( "script", s.dataTypes ) > -1 && + jQuery.inArray( "json", s.dataTypes ) < 0 ) { + s.converters[ "text script" ] = function() {}; + } + + // Convert no matter what (that way responseXXX fields are always set) + response = ajaxConvert( s, response, jqXHR, isSuccess ); + + // If successful, handle type chaining + if ( isSuccess ) { + + // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. + if ( s.ifModified ) { + modified = jqXHR.getResponseHeader( "Last-Modified" ); + if ( modified ) { + jQuery.lastModified[ cacheURL ] = modified; + } + modified = jqXHR.getResponseHeader( "etag" ); + if ( modified ) { + jQuery.etag[ cacheURL ] = modified; + } + } + + // if no content + if ( status === 204 || s.type === "HEAD" ) { + statusText = "nocontent"; + + // if not modified + } else if ( status === 304 ) { + statusText = "notmodified"; + + // If we have data, let's convert it + } else { + statusText = response.state; + success = response.data; + error = response.error; + isSuccess = !error; + } + } else { + + // Extract error from statusText and normalize for non-aborts + error = statusText; + if ( status || !statusText ) { + statusText = "error"; + if ( status < 0 ) { + status = 0; + } + } + } + + // Set data for the fake xhr object + jqXHR.status = status; + jqXHR.statusText = ( nativeStatusText || statusText ) + ""; + + // Success/Error + if ( isSuccess ) { + deferred.resolveWith( callbackContext, [ success, statusText, jqXHR ] ); + } else { + deferred.rejectWith( callbackContext, [ jqXHR, statusText, error ] ); + } + + // Status-dependent callbacks + jqXHR.statusCode( statusCode ); + statusCode = undefined; + + if ( fireGlobals ) { + globalEventContext.trigger( isSuccess ? "ajaxSuccess" : "ajaxError", + [ jqXHR, s, isSuccess ? success : error ] ); + } + + // Complete + completeDeferred.fireWith( callbackContext, [ jqXHR, statusText ] ); + + if ( fireGlobals ) { + globalEventContext.trigger( "ajaxComplete", [ jqXHR, s ] ); + + // Handle the global AJAX counter + if ( !( --jQuery.active ) ) { + jQuery.event.trigger( "ajaxStop" ); + } + } + } + + return jqXHR; + }, + + getJSON: function( url, data, callback ) { + return jQuery.get( url, data, callback, "json" ); + }, + + getScript: function( url, callback ) { + return jQuery.get( url, undefined, callback, "script" ); + } +} ); + +jQuery.each( [ "get", "post" ], function( _i, method ) { + jQuery[ method ] = function( url, data, callback, type ) { + + // Shift arguments if data argument was omitted + if ( isFunction( data ) ) { + type = type || callback; + callback = data; + data = undefined; + } + + // The url can be an options object (which then must have .url) + return jQuery.ajax( jQuery.extend( { + url: url, + type: method, + dataType: type, + data: data, + success: callback + }, jQuery.isPlainObject( url ) && url ) ); + }; +} ); + +jQuery.ajaxPrefilter( function( s ) { + var i; + for ( i in s.headers ) { + if ( i.toLowerCase() === "content-type" ) { + s.contentType = s.headers[ i ] || ""; + } + } +} ); + + +jQuery._evalUrl = function( url, options, doc ) { + return jQuery.ajax( { + url: url, + + // Make this explicit, since user can override this through ajaxSetup (#11264) + type: "GET", + dataType: "script", + cache: true, + async: false, + global: false, + + // Only evaluate the response if it is successful (gh-4126) + // dataFilter is not invoked for failure responses, so using it instead + // of the default converter is kludgy but it works. + converters: { + "text script": function() {} + }, + dataFilter: function( response ) { + jQuery.globalEval( response, options, doc ); + } + } ); +}; + + +jQuery.fn.extend( { + wrapAll: function( html ) { + var wrap; + + if ( this[ 0 ] ) { + if ( isFunction( html ) ) { + html = html.call( this[ 0 ] ); + } + + // The elements to wrap the target around + wrap = jQuery( html, this[ 0 ].ownerDocument ).eq( 0 ).clone( true ); + + if ( this[ 0 ].parentNode ) { + wrap.insertBefore( this[ 0 ] ); + } + + wrap.map( function() { + var elem = this; + + while ( elem.firstElementChild ) { + elem = elem.firstElementChild; + } + + return elem; + } ).append( this ); + } + + return this; + }, + + wrapInner: function( html ) { + if ( isFunction( html ) ) { + return this.each( function( i ) { + jQuery( this ).wrapInner( html.call( this, i ) ); + } ); + } + + return this.each( function() { + var self = jQuery( this ), + contents = self.contents(); + + if ( contents.length ) { + contents.wrapAll( html ); + + } else { + self.append( html ); + } + } ); + }, + + wrap: function( html ) { + var htmlIsFunction = isFunction( html ); + + return this.each( function( i ) { + jQuery( this ).wrapAll( htmlIsFunction ? html.call( this, i ) : html ); + } ); + }, + + unwrap: function( selector ) { + this.parent( selector ).not( "body" ).each( function() { + jQuery( this ).replaceWith( this.childNodes ); + } ); + return this; + } +} ); + + +jQuery.expr.pseudos.hidden = function( elem ) { + return !jQuery.expr.pseudos.visible( elem ); +}; +jQuery.expr.pseudos.visible = function( elem ) { + return !!( elem.offsetWidth || elem.offsetHeight || elem.getClientRects().length ); +}; + + + + +jQuery.ajaxSettings.xhr = function() { + try { + return new window.XMLHttpRequest(); + } catch ( e ) {} +}; + +var xhrSuccessStatus = { + + // File protocol always yields status code 0, assume 200 + 0: 200, + + // Support: IE <=9 only + // #1450: sometimes IE returns 1223 when it should be 204 + 1223: 204 + }, + xhrSupported = jQuery.ajaxSettings.xhr(); + +support.cors = !!xhrSupported && ( "withCredentials" in xhrSupported ); +support.ajax = xhrSupported = !!xhrSupported; + +jQuery.ajaxTransport( function( options ) { + var callback, errorCallback; + + // Cross domain only allowed if supported through XMLHttpRequest + if ( support.cors || xhrSupported && !options.crossDomain ) { + return { + send: function( headers, complete ) { + var i, + xhr = options.xhr(); + + xhr.open( + options.type, + options.url, + options.async, + options.username, + options.password + ); + + // Apply custom fields if provided + if ( options.xhrFields ) { + for ( i in options.xhrFields ) { + xhr[ i ] = options.xhrFields[ i ]; + } + } + + // Override mime type if needed + if ( options.mimeType && xhr.overrideMimeType ) { + xhr.overrideMimeType( options.mimeType ); + } + + // X-Requested-With header + // For cross-domain requests, seeing as conditions for a preflight are + // akin to a jigsaw puzzle, we simply never set it to be sure. + // (it can always be set on a per-request basis or even using ajaxSetup) + // For same-domain requests, won't change header if already provided. + if ( !options.crossDomain && !headers[ "X-Requested-With" ] ) { + headers[ "X-Requested-With" ] = "XMLHttpRequest"; + } + + // Set headers + for ( i in headers ) { + xhr.setRequestHeader( i, headers[ i ] ); + } + + // Callback + callback = function( type ) { + return function() { + if ( callback ) { + callback = errorCallback = xhr.onload = + xhr.onerror = xhr.onabort = xhr.ontimeout = + xhr.onreadystatechange = null; + + if ( type === "abort" ) { + xhr.abort(); + } else if ( type === "error" ) { + + // Support: IE <=9 only + // On a manual native abort, IE9 throws + // errors on any property access that is not readyState + if ( typeof xhr.status !== "number" ) { + complete( 0, "error" ); + } else { + complete( + + // File: protocol always yields status 0; see #8605, #14207 + xhr.status, + xhr.statusText + ); + } + } else { + complete( + xhrSuccessStatus[ xhr.status ] || xhr.status, + xhr.statusText, + + // Support: IE <=9 only + // IE9 has no XHR2 but throws on binary (trac-11426) + // For XHR2 non-text, let the caller handle it (gh-2498) + ( xhr.responseType || "text" ) !== "text" || + typeof xhr.responseText !== "string" ? + { binary: xhr.response } : + { text: xhr.responseText }, + xhr.getAllResponseHeaders() + ); + } + } + }; + }; + + // Listen to events + xhr.onload = callback(); + errorCallback = xhr.onerror = xhr.ontimeout = callback( "error" ); + + // Support: IE 9 only + // Use onreadystatechange to replace onabort + // to handle uncaught aborts + if ( xhr.onabort !== undefined ) { + xhr.onabort = errorCallback; + } else { + xhr.onreadystatechange = function() { + + // Check readyState before timeout as it changes + if ( xhr.readyState === 4 ) { + + // Allow onerror to be called first, + // but that will not handle a native abort + // Also, save errorCallback to a variable + // as xhr.onerror cannot be accessed + window.setTimeout( function() { + if ( callback ) { + errorCallback(); + } + } ); + } + }; + } + + // Create the abort callback + callback = callback( "abort" ); + + try { + + // Do send the request (this may raise an exception) + xhr.send( options.hasContent && options.data || null ); + } catch ( e ) { + + // #14683: Only rethrow if this hasn't been notified as an error yet + if ( callback ) { + throw e; + } + } + }, + + abort: function() { + if ( callback ) { + callback(); + } + } + }; + } +} ); + + + + +// Prevent auto-execution of scripts when no explicit dataType was provided (See gh-2432) +jQuery.ajaxPrefilter( function( s ) { + if ( s.crossDomain ) { + s.contents.script = false; + } +} ); + +// Install script dataType +jQuery.ajaxSetup( { + accepts: { + script: "text/javascript, application/javascript, " + + "application/ecmascript, application/x-ecmascript" + }, + contents: { + script: /\b(?:java|ecma)script\b/ + }, + converters: { + "text script": function( text ) { + jQuery.globalEval( text ); + return text; + } + } +} ); + +// Handle cache's special case and crossDomain +jQuery.ajaxPrefilter( "script", function( s ) { + if ( s.cache === undefined ) { + s.cache = false; + } + if ( s.crossDomain ) { + s.type = "GET"; + } +} ); + +// Bind script tag hack transport +jQuery.ajaxTransport( "script", function( s ) { + + // This transport only deals with cross domain or forced-by-attrs requests + if ( s.crossDomain || s.scriptAttrs ) { + var script, callback; + return { + send: function( _, complete ) { + script = jQuery( " + <%= stylesheet_link_tag :langstyle, media: :langstyle %> <% if current_page.data.search %> <%= javascript_include_tag "all" %> <% else %> <%= javascript_include_tag "all_nosearch" %> <% end %> + <%= javascript_include_tag "lazyload" %> + + + + + <% if current_page.data.code_clipboard %> + <% end %> - - - - NAV - <%= image_tag('navbar.png') %> - - -
      - <%= image_tag "logo.png" %> - <% if language_tabs %> -
      - <% language_tabs.each do |lang| %> - <% if lang.is_a? Hash %> - <%= lang.values.first %> - <% else %> - <%= lang %> - <% end %> - <% end %> -
      - <% end %> - <% if current_page.data.search %> -