├── CODE_OF_CONDUCT.md ├── LICENSE ├── README.md ├── backend ├── APP │ ├── category_get │ │ ├── category_get.py │ │ └── requirements.txt │ ├── dynamodb_data │ │ ├── table_order_item_0.json │ │ ├── table_order_item_1.json │ │ ├── table_order_item_2.json │ │ ├── table_order_item_3.json │ │ └── table_order_item_5.json │ ├── item_list_get │ │ ├── item_list_get.py │ │ └── requirements.txt │ ├── order_info_get │ │ ├── order_info_get.py │ │ └── requirements.txt │ ├── order_put │ │ ├── order_put.py │ │ └── requirements.txt │ ├── payment_confirm │ │ ├── confirm.py │ │ └── requirements.txt │ ├── payment_confirm_nolinepay │ │ ├── confirm_nolinepay.py │ │ └── requirements.txt │ ├── payment_id_get │ │ ├── payment_id_get.py │ │ └── requirements.txt │ ├── payment_reserve │ │ ├── requirements.txt │ │ └── reserve.py │ ├── samconfig.toml │ └── template.yaml ├── Layer │ ├── .DS_Store │ ├── layer │ │ ├── .DS_Store │ │ ├── aws │ │ │ └── dynamodb │ │ │ │ └── base.py │ │ ├── common │ │ │ ├── channel_access_token.py │ │ │ ├── common_const.py │ │ │ ├── const.py │ │ │ ├── line.py │ │ │ └── utils.py │ │ ├── requirements.txt │ │ ├── table_order │ │ │ ├── table_order_const.py │ │ │ ├── table_order_item_list.py │ │ │ └── table_order_payment_order_info.py │ │ └── validation │ │ │ ├── param_check.py │ │ │ └── table_order_param_check.py │ ├── samconfig.toml │ └── template.yaml └── batch │ ├── samconfig.toml │ ├── template.yaml │ └── update_line_access_token │ ├── requirements.txt │ └── update_line_access_token.py ├── docs ├── en │ ├── README_en.md │ ├── back-end-construction.md │ ├── front-end-construction.md │ ├── front-end-development-environment.md │ ├── liff-channel-create.md │ ├── test-data-charge.md │ └── validation.md ├── images │ ├── channel-access-token-table-record-en.png │ ├── en │ │ ├── channel-access-token-table-record-en.png │ │ ├── end-point-url-description-en.png │ │ ├── end-point-url-editing-en.png │ │ ├── liff-console-en.png │ │ ├── line-channel-create-1-en.png │ │ ├── line-channel-create-2-en.png │ │ ├── line-channel-create-3-en.png │ │ ├── line-channel-create-4-en.png │ │ ├── line-channel-create-add-liff-app-en.png │ │ ├── line-channel-create-add-liff-en.png │ │ ├── line-provider-create-1-en.png │ │ ├── line-provider-create-2-en.png │ │ ├── linepay-channel-information-en.png │ │ ├── linepay-key-en.png │ │ ├── out-put-description-en.png │ │ ├── test-data-charge-en.png │ │ └── test-event-set-en.png │ ├── end-point-url-description-en.png │ ├── end-point-url-editing-en.png │ ├── jp │ │ ├── channel-access-token-table-record.png │ │ ├── end-point-url-description.png │ │ ├── end-point-url-editing.png │ │ ├── liff-console.png │ │ ├── line-channel-create-1.png │ │ ├── line-channel-create-2.png │ │ ├── line-channel-create-3.png │ │ ├── line-channel-create-4.png │ │ ├── line-channel-create-add-liff-app.png │ │ ├── line-channel-create-add-liff.png │ │ ├── line-provider-create-1.png │ │ ├── line-provider-create-2.png │ │ ├── linepay-channel-information.png │ │ ├── linepay-key.png │ │ ├── out-put-description.png │ │ ├── test-data-charge.png │ │ └── test-event-set.png │ ├── liff-console-en.png │ ├── line-channel-create-1-en.png │ ├── line-channel-create-2-en.png │ ├── line-channel-create-3-en.png │ ├── line-channel-create-4-en.png │ ├── line-channel-create-add-liff-app-en.png │ ├── line-channel-create-add-liff-en.png │ ├── line-provider-create-1-en.png │ ├── line-provider-create-2-en.png │ ├── linepay-channel-information-en.png │ ├── linepay-key-en.png │ ├── out-put-description-en.png │ ├── test-data-charge-en.png │ └── test-event-set-en.png └── jp │ ├── back-end-construction.md │ ├── front-end-construction.md │ ├── front-end-development-environment.md │ ├── liff-channel-create.md │ ├── test-data-charge.md │ └── validation.md └── front ├── .editorconfig ├── .eslintrc.json ├── .gitignore ├── assets ├── css │ └── style.css ├── img │ ├── line.png │ ├── line_pay.png │ └── line_pay2.png └── sass │ ├── _variables.scss │ └── app.scss ├── cert └── ここにSSL証明書のcrtファイル、 keyファイルを配置してください ├── components ├── ErrorModal.vue └── tableorder │ ├── Header.vue │ ├── MenuCard.vue │ └── Ordered.vue ├── layouts ├── default.vue └── tableorder │ └── order.vue ├── locales └── ja.json ├── middleware └── initialize.js ├── nuxt.config.js ├── package-lock.json ├── package.json ├── pages ├── error │ └── 404.vue ├── index.vue └── tableorder │ ├── basket.vue │ ├── completed.vue │ ├── menu │ └── _seatNo.vue │ ├── payment.vue │ └── paymentCompleted.vue ├── plugins ├── amplify.js ├── app │ └── tableorder.js ├── axiosEx.js ├── flashMessage.js ├── i18n.js ├── liff.js ├── localStorage.js ├── noreload.js ├── processing.js ├── sessionStorage.js ├── utils.js └── vuetify.js ├── static └── favicon.ico ├── store └── index.js └── yarn.lock /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | 2 | # Contributor Covenant Code of Conduct 3 | 4 | ## Our Pledge 5 | 6 | We as members, contributors, and leaders pledge to make participation in our 7 | community a harassment-free experience for everyone, regardless of age, body 8 | size, visible or invisible disability, ethnicity, sex characteristics, gender 9 | identity and expression, level of experience, education, socio-economic status, 10 | nationality, personal appearance, race, religion, or sexual identity 11 | and orientation. 12 | 13 | We pledge to act and interact in ways that contribute to an open, welcoming, 14 | diverse, inclusive, and healthy community. 15 | 16 | ## Our Standards 17 | 18 | Examples of behavior that contributes to a positive environment for our 19 | community include: 20 | 21 | * Demonstrating empathy and kindness toward other people 22 | * Being respectful of differing opinions, viewpoints, and experiences 23 | * Giving and gracefully accepting constructive feedback 24 | * Accepting responsibility and apologizing to those affected by our mistakes, 25 | and learning from the experience 26 | * Focusing on what is best not just for us as individuals, but for the 27 | overall community 28 | 29 | Examples of unacceptable behavior include: 30 | 31 | * The use of sexualized language or imagery, and sexual attention or 32 | advances of any kind 33 | * Trolling, insulting or derogatory comments, and personal or political attacks 34 | * Public or private harassment 35 | * Publishing others' private information, such as a physical or email 36 | address, without their explicit permission 37 | * Other conduct which could reasonably be considered inappropriate in a 38 | professional setting 39 | 40 | ## Enforcement Responsibilities 41 | 42 | Community leaders are responsible for clarifying and enforcing our standards of 43 | acceptable behavior and will take appropriate and fair corrective action in 44 | response to any behavior that they deem inappropriate, threatening, offensive, 45 | or harmful. 46 | 47 | Community leaders have the right and responsibility to remove, edit, or reject 48 | comments, commits, code, wiki edits, issues, and other contributions that are 49 | not aligned to this Code of Conduct, and will communicate reasons for moderation 50 | decisions when appropriate. 51 | 52 | ## Scope 53 | 54 | This Code of Conduct applies within all community spaces, and also applies when 55 | an individual is officially representing the community in public spaces. 56 | Examples of representing our community include using an official e-mail address, 57 | posting via an official social media account, or acting as an appointed 58 | representative at an online or offline event. 59 | 60 | ## Enforcement 61 | 62 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 63 | reported to the community leaders responsible for enforcement at 64 | [dl_oss_dev@linecorp.com](mailto:dl_oss_linecorp.com). 65 | All complaints will be reviewed and investigated promptly and fairly. 66 | 67 | All community leaders are obligated to respect the privacy and security of the 68 | reporter of any incident. 69 | 70 | ## Enforcement Guidelines 71 | 72 | Community leaders will follow these Community Impact Guidelines in determining 73 | the consequences for any action they deem in violation of this Code of Conduct: 74 | 75 | ### 1. Correction 76 | 77 | **Community Impact**: Use of inappropriate language or other behavior deemed 78 | unprofessional or unwelcome in the community. 79 | 80 | **Consequence**: A private, written warning from community leaders, providing 81 | clarity around the nature of the violation and an explanation of why the 82 | behavior was inappropriate. A public apology may be requested. 83 | 84 | ### 2. Warning 85 | 86 | **Community Impact**: A violation through a single incident or series 87 | of actions. 88 | 89 | **Consequence**: A warning with consequences for continued behavior. No 90 | interaction with the people involved, including unsolicited interaction with 91 | those enforcing the Code of Conduct, for a specified period of time. This 92 | includes avoiding interactions in community spaces as well as external channels 93 | like social media. Violating these terms may lead to a temporary or 94 | permanent ban. 95 | 96 | ### 3. Temporary Ban 97 | 98 | **Community Impact**: A serious violation of community standards, including 99 | sustained inappropriate behavior. 100 | 101 | **Consequence**: A temporary ban from any sort of interaction or public 102 | communication with the community for a specified period of time. No public or 103 | private interaction with the people involved, including unsolicited interaction 104 | with those enforcing the Code of Conduct, is allowed during this period. 105 | Violating these terms may lead to a permanent ban. 106 | 107 | ### 4. Permanent Ban 108 | 109 | **Community Impact**: Demonstrating a pattern of violation of community 110 | standards, including sustained inappropriate behavior, harassment of an 111 | individual, or aggression toward or disparagement of classes of individuals. 112 | 113 | **Consequence**: A permanent ban from any sort of public interaction within 114 | the community. 115 | 116 | ## Attribution 117 | 118 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 119 | version 2.0, available at 120 | [https://www.contributor-covenant.org/version/2/0/code_of_conduct.html][v2.0]. 121 | 122 | Community Impact Guidelines were inspired by 123 | [Mozilla's code of conduct enforcement ladder][Mozilla CoC]. 124 | 125 | For answers to common questions about this code of conduct, see the FAQ at 126 | [https://www.contributor-covenant.org/faq][FAQ]. Translations are available 127 | at [https://www.contributor-covenant.org/translations][translations]. 128 | 129 | [homepage]: https://www.contributor-covenant.org 130 | [v2.0]: https://www.contributor-covenant.org/version/2/0/code_of_conduct.html 131 | [Mozilla CoC]: https://github.com/mozilla/diversity 132 | [FAQ]: https://www.contributor-covenant.org/faq 133 | [translations]: https://www.contributor-covenant.org/translations 134 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Overview 2 | [LINE API Use Caseサイト](https://lineapiusecase.com/ja/top.html)で提供している[テーブルオーダー](https://lineapiusecase.com/ja/usecase/tableorder.html)のデモアプリケーションソースコードとなります。 3 | 今回紹介している手順を参考にすると、LINE APIを活用したテーブルオーダーアプリケーションを開発することが可能です。 4 | テーブルオーダーアプリケーションを利用すると、LINEアプリ上で飲食店のメニューを表示し、注文~決済を行うことが出来ます。そのため、店舗で注文用に独自のハードウェアを持つ必要が無くなります。 5 | さらに、決済後にLIFFアプリで取得したユーザーIDを利用し、LINEで販促メッセージを送信することも出来ます。 6 | 7 | なお、このページで紹介しているソースコードの環境はAWSを利用しています。 8 | 9 | ※ [The English version document is here.](./docs/en/README_en.md) 10 | 11 | # Libraries 12 | ## Node.js 13 | フロントエンド側の開発で使用する Node.js をローカル開発環境にインストールしてください。 14 | ※ v10.13 以上 最新の LTS バージョンのインストールをおすすめします 15 | 16 | 【Node.jsダウンロードサイト】 17 | https://nodejs.org/ja/download/ 18 | 19 | ## Python 20 | Pythonのバージョン3.8以上がインストール済みでない場合、インストールしてください。 21 | コマンドプロンプト、又はターミナルにて以下のコマンドを入力し、インストール済みか確認できます。 22 | ``` 23 | python --version 24 | 25 | Python 3.8.3 ← このように表示されたら、インストール済みです。 26 | ``` 27 | 28 | インストール済みでない場合、バックエンド側の開発で使用するPython(3.8以上)をローカル開発環境にインストールしてください。 29 | 30 | 【Pythonインストール参考サイト】 31 | Windows: https://www.python.jp/install/windows/install.html 32 | Mac: https://www.python.jp/install/macos/index.html 33 | 34 | ## AWS SAM 35 | 36 | ※ 注意:5月12日現在AWS側で不具合があり、コマンド実行時にエラーとなる可能性があります。実行時にエラーが出た場合は、該当ファイルの該当プロパティを削除してください。 37 | 38 | 本アプリケーションのデプロイには、AWS サーバーレスアプリケーションモデル(AWS SAM)を利用します。 39 | [AWS公式ドキュメント](https://docs.aws.amazon.com/ja_jp/serverless-application-model/latest/developerguide/serverless-sam-cli-install.html 40 | )を参考に、AWS アカウントの登録と設定、AWS SAM CLI と Docker のインストールを行ってください。 41 | ※ SAM CLIの推奨バージョンは1.15.0以上 42 | ※ Docker のインストールもローカルテストの有無に関わらず必要です。 43 | 44 | ### 公式ドキュメントの参考箇所 45 | 公式ドキュメントの以下の項目を完了させ、次の手順に進んでください。なお、既に導入済みのものは適宜飛ばして下さい。 46 | ※本資料は 2020 年 12 月に作成しているため、最新の公式ドキュメントの内容と齟齬がある可能性があります。 47 | 48 | 1. AWS SAM CLI のインストール 49 | 1. AWS 認証情報の設定 50 | 1. (任意)チュートリアル: Hello World アプリケーションの導入 51 | 52 | # Getting Started / Tutorial 53 | こちらの手順では、アプリケーション開発に必要な「LINEチャネル作成、バックエンド・フロントエンドの構築、テストデータ投入、動作確認」について説明します。 54 | 以下リンク先の手順を参考にし、本番環境(AWS)とローカル環境の構築を行ってください。 55 | 56 | ### [LINE チャネルの作成](./docs/jp/liff-channel-create.md) 57 | ### [バックエンドの構築](./docs/jp/back-end-construction.md) 58 | ### [本番(AWS)フロントエンド環境構築](./docs/jp/front-end-construction.md) 59 | ### [ローカルフロントエンド環境構築](./docs/jp/front-end-development-environment.md) 60 | *** 61 | ### [テストデータ投入](./docs/jp/test-data-charge.md) 62 | *** 63 | ### [動作確認](./docs/jp/validation.md) 64 | *** 65 | # License 66 | TableOrderの全てのファイルは、条件なしで自由にご利用いただけます。 67 | 自由にdownload&cloneをして、LINE APIを活用した素敵なアプリケーションの開発を始めてください! 68 | 69 | See [LICENSE](LICENSE) for more detail.(English) 70 | 71 | # How to contribute 72 | 73 | First of all, thank you so much for taking your time to contribute! LINE API Use Case Hair Salon is not very different from any other open source projects. It will be fantastic if you help us by doing any of the following: 74 | 75 | - File an issue in [the issue tracker](https://github.com/line/line-api-use-case-table-order/issues) to report bugs and propose new features and improvements. 76 | - Ask a question using [the issue tracker](https://github.com/line/line-api-use-case-table-order/issues). 77 | - Contribute your work by sending [a pull request](https://github.com/line/line-api-use-case-table-order/pulls). 78 | 79 | When you are sending a pull request, you'll be considered as being aware of and accepting the followings. 80 | - Grant [the same license](LICENSE) to the contribution 81 | - Represent the contribution is your own creation 82 | - Not expected to provide support for your contribution 83 | -------------------------------------------------------------------------------- /backend/APP/category_get/category_get.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import json 3 | import os 4 | from common import utils 5 | from table_order.table_order_item_list import TableOrderItemList 6 | # 環境変数の取得 7 | LOGGER_LEVEL = os.environ.get("LOGGER_LEVEL") 8 | # ログ出力の設定 9 | logger = logging.getLogger() 10 | if LOGGER_LEVEL == 'DEBUG': 11 | logger.setLevel(logging.DEBUG) 12 | else: 13 | logger.setLevel(logging.INFO) 14 | 15 | # ログ出力の設定 16 | logger = logging.getLogger() 17 | logger.setLevel(logging.INFO) 18 | 19 | # テーブル操作クラスの初期化 20 | item_master_table_controller = TableOrderItemList() 21 | 22 | 23 | def get_category(): 24 | """ 25 | カテゴリIDとカテゴリ名を返却する 26 | 27 | Returns 28 | ------- 29 | categories:dict 30 | 商品カテゴリ一覧情報 31 | """ 32 | 33 | categories = item_master_table_controller.scan() 34 | for category in categories: 35 | category.pop('items') 36 | return categories 37 | 38 | 39 | def lambda_handler(event, context): 40 | """ 41 | 商品カテゴリ情報を返す 42 | Parameters 43 | ---------- 44 | event : dict 45 | フロントより渡されたパラメータ 46 | context : dict 47 | コンテキスト内容。 48 | Returns 49 | ------- 50 | categories : dict 51 | 商品カテゴリ一覧情報 52 | """ 53 | # パラメータログ 54 | logger.info(event) 55 | try: 56 | categories = get_category() 57 | except Exception as e: 58 | logger.error('Occur Exception: %s', e) 59 | return utils.create_error_response('ERROR') 60 | 61 | return utils.create_success_response( 62 | json.dumps(categories, default=utils.decimal_to_int, 63 | ensure_ascii=False)) 64 | -------------------------------------------------------------------------------- /backend/APP/category_get/requirements.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/line/line-api-use-case-table-order/40f420b8e6e4f1a90ef65c3fd26b9ef1c0555ef9/backend/APP/category_get/requirements.txt -------------------------------------------------------------------------------- /backend/APP/dynamodb_data/table_order_item_0.json: -------------------------------------------------------------------------------- 1 | { 2 | "categoryId": 0, 3 | "categoryName": "オススメ", 4 | "items": [ 5 | { 6 | "categoryId": 0, 7 | "categoryName": "オススメ", 8 | "discountRate": 20, 9 | "discountWay": 1, 10 | "imageUrl": "https://media.istockphoto.com/photos/french-fries-on-white-background-picture-id604378894?s=2048x2048", 11 | "itemDespription": "国産じゃがいも使用。ケチャップでどうぞ。", 12 | "itemId": 1, 13 | "itemName": "ポテトフライ", 14 | "orderNo": 1, 15 | "price": 190, 16 | "stockoutFlg": false 17 | }, 18 | { 19 | "categoryId": 0, 20 | "categoryName": "オススメ", 21 | "discountRate": 10, 22 | "discountWay": 2, 23 | "imageUrl": "https://media.istockphoto.com/photos/japanese-fried-chicken-picture-id1150109573?s=2048x2048", 24 | "itemDespription": "サクサク、ジューシーな味をお楽しみください。", 25 | "itemId": 2, 26 | "itemName": "特製唐揚げ", 27 | "orderNo": 1, 28 | "price": 500, 29 | "stockoutFlg": false 30 | }, 31 | { 32 | "categoryId": 0, 33 | "categoryName": "オススメ", 34 | "discountRate": 0, 35 | "discountWay": 0, 36 | "imageUrl": "https://media.istockphoto.com/photos/green-salad-with-fresh-vegetables-picture-id953810510?s=2048x2048", 37 | "itemDespription": "人気のサラダです。", 38 | "itemId": 3, 39 | "itemName": "グリーンサラダ", 40 | "orderNo": 1, 41 | "price": 390, 42 | "stockoutFlg": false 43 | } 44 | ], 45 | "orderNo": 1 46 | } -------------------------------------------------------------------------------- /backend/APP/dynamodb_data/table_order_item_1.json: -------------------------------------------------------------------------------- 1 | { 2 | "categoryId": 1, 3 | "categoryName": "ドリンク", 4 | "items": [ 5 | { 6 | "categoryId": 1, 7 | "categoryName": "ドリンク", 8 | "discountRate": 0, 9 | "discountWay": 0, 10 | "imageUrl": "https://media.istockphoto.com/photos/glass-of-beer-isolated-on-white-background-picture-id862774556?s=2048x2048", 11 | "itemDespription": "まずはビールから!", 12 | "itemId": 1001, 13 | "itemName": "生ビール", 14 | "orderNo": 1, 15 | "price": 300, 16 | "stockoutFlg": false 17 | }, 18 | { 19 | "categoryId": 1, 20 | "categoryName": "ドリンク", 21 | "discountRate": 0, 22 | "discountWay": 0, 23 | "imageUrl": "https://media.istockphoto.com/photos/glass-of-gin-tonic-picture-id803905662?s=2048x2048", 24 | "itemDespription": "フルーティーで優しい口当たり。", 25 | "itemId": 1002, 26 | "itemName": "特製カクテル", 27 | "orderNo": 2, 28 | "price": 350, 29 | "stockoutFlg": false 30 | }, 31 | { 32 | "categoryId": 1, 33 | "categoryName": "ドリンク", 34 | "discountRate": 0, 35 | "discountWay": 0, 36 | "imageUrl": "https://media.istockphoto.com/photos/cola-with-crushed-ice-and-straw-in-tall-glass-picture-id681018122?s=2048x2048", 37 | "itemDespription": "", 38 | "itemId": 1003, 39 | "itemName": "コーラ", 40 | "orderNo": 3, 41 | "price": 200, 42 | "stockoutFlg": false 43 | } 44 | ], 45 | "orderNo": 2 46 | } -------------------------------------------------------------------------------- /backend/APP/dynamodb_data/table_order_item_2.json: -------------------------------------------------------------------------------- 1 | { 2 | "categoryId": 2, 3 | "categoryName": "スピードメニュー", 4 | "items": [ 5 | { 6 | "categoryId": 2, 7 | "categoryName": "スピードメニュー", 8 | "discountRate": 0, 9 | "discountWay": 0, 10 | "imageUrl": "https://media.istockphoto.com/photos/edamame-japanese-foodboiled-green-soybeans-picture-id1066735192?s=2048x2048", 11 | "itemDespription": "甘み引き立つうす塩味に仕上げました。", 12 | "itemId": 2005, 13 | "itemName": "枝豆", 14 | "orderNo": 6, 15 | "price": 190, 16 | "stockoutFlg": false 17 | }, 18 | { 19 | "categoryId": 2, 20 | "categoryName": "スピードメニュー", 21 | "discountRate": 20, 22 | "discountWay": 1, 23 | "imageUrl": "https://media.istockphoto.com/photos/japanese-food-japanese-soft-cold-tofu-picture-id1170639696?s=2048x2048", 24 | "itemDespription": "", 25 | "itemId": 2006, 26 | "itemName": "冷ややっこ", 27 | "orderNo": 6, 28 | "price": 200, 29 | "stockoutFlg": false 30 | } 31 | ], 32 | "orderNo": 3 33 | } -------------------------------------------------------------------------------- /backend/APP/dynamodb_data/table_order_item_3.json: -------------------------------------------------------------------------------- 1 | { 2 | "categoryId": 3, 3 | "categoryName": "料理", 4 | "items": [ 5 | { 6 | "categoryId": 3, 7 | "categoryName": "料理", 8 | "discountRate": 10, 9 | "discountWay": 2, 10 | "imageUrl": "https://media.istockphoto.com/photos/japanese-fried-chicken-picture-id1150109573?s=2048x2048", 11 | "itemDespription": "サクサク、ジューシーな味をお楽しみください。", 12 | "itemId": 3001, 13 | "itemName": "特製唐揚げ", 14 | "orderNo": 1, 15 | "price": 500, 16 | "stockoutFlg": false 17 | }, 18 | { 19 | "categoryId": 3, 20 | "categoryName": "料理", 21 | "discountRate": 20, 22 | "discountWay": 1, 23 | "imageUrl": "https://media.istockphoto.com/photos/french-fries-on-white-background-picture-id604378894?s=2048x2048", 24 | "itemDespription": "国産じゃがいも使用。ケチャップでどうぞ。", 25 | "itemId": 3002, 26 | "itemName": "ポテトフライ", 27 | "orderNo": 1, 28 | "price": 190, 29 | "stockoutFlg": false 30 | } 31 | ], 32 | "orderNo": 4 33 | } -------------------------------------------------------------------------------- /backend/APP/dynamodb_data/table_order_item_5.json: -------------------------------------------------------------------------------- 1 | { 2 | "categoryId": 5, 3 | "categoryName": "本日限定品", 4 | "items": [ 5 | { 6 | "categoryId": 5, 7 | "categoryName": "本日限定品", 8 | "discountRate": 0, 9 | "discountWay": 0, 10 | "imageUrl": "https://media.istockphoto.com/photos/isolated-top-view-of-sukiyaki-hot-pot-with-boiling-vegetables-picture-id1090355486?s=2048x2048", 11 | "itemDespription": "", 12 | "itemId": 5001, 13 | "itemName": "寄せ鍋", 14 | "orderNo": 1, 15 | "price": 1200, 16 | "stockoutFlg": false 17 | } 18 | ], 19 | "orderNo": 5 20 | } -------------------------------------------------------------------------------- /backend/APP/item_list_get/item_list_get.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import json 3 | import os 4 | from common import utils 5 | from table_order.table_order_item_list import TableOrderItemList 6 | 7 | # 環境変数の取得 8 | LOGGER_LEVEL = os.environ.get("LOGGER_LEVEL") 9 | # ログ出力の設定 10 | logger = logging.getLogger() 11 | if LOGGER_LEVEL == 'DEBUG': 12 | logger.setLevel(logging.DEBUG) 13 | else: 14 | logger.setLevel(logging.INFO) 15 | 16 | # テーブル操作クラスの初期化 17 | item_master_table_controller = TableOrderItemList() 18 | 19 | 20 | def get_item_list(params): 21 | """ 22 | 指定のカテゴリの商品情報一覧を取得する 23 | 24 | Parameters 25 | ---------- 26 | params : dict 27 | APIGatewayのGETパラメータ 28 | 29 | Returns 30 | ------- 31 | items:dict 32 | 商品情報一覧 33 | """ 34 | 35 | category_id = 1 36 | if 'categoryId' in params and params['categoryId']: 37 | category_id = int(params['categoryId']) 38 | 39 | items = item_master_table_controller.get_item(category_id) 40 | logger.debug('items %s', items) 41 | return items 42 | 43 | 44 | def lambda_handler(event, context): 45 | """ 46 | 商品情報を返す 47 | Parameters 48 | ---------- 49 | event : dict 50 | フロントより渡されたパラメータ 51 | context : dict 52 | コンテキスト内容 53 | Returns 54 | ------- 55 | items : dict 56 | 商品情報 57 | """ 58 | # パラメータログ 59 | logger.info(event) 60 | 61 | try: 62 | items = get_item_list(event['queryStringParameters']) 63 | except Exception as e: 64 | logger.error('Occur Exception: %s', e) 65 | return utils.create_error_response('ERROR') 66 | 67 | return utils.create_success_response( 68 | json.dumps(items, default=utils.decimal_to_int, 69 | ensure_ascii=False)) 70 | -------------------------------------------------------------------------------- /backend/APP/item_list_get/requirements.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/line/line-api-use-case-table-order/40f420b8e6e4f1a90ef65c3fd26b9ef1c0555ef9/backend/APP/item_list_get/requirements.txt -------------------------------------------------------------------------------- /backend/APP/order_info_get/order_info_get.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import json 3 | import os 4 | 5 | from common import (common_const, utils) 6 | from validation.table_order_param_check import TableOrderParamCheck 7 | from table_order.table_order_payment_order_info import TableOrderPaymentOrderInfo # noqa501 8 | 9 | 10 | # 環境変数の取得 11 | LOGGER_LEVEL = os.environ.get("LOGGER_LEVEL") 12 | # ログ出力の設定 13 | logger = logging.getLogger() 14 | if LOGGER_LEVEL == 'DEBUG': 15 | logger.setLevel(logging.DEBUG) 16 | else: 17 | logger.setLevel(logging.INFO) 18 | 19 | # テーブル操作クラスの初期化 20 | payment_order_table_controller = TableOrderPaymentOrderInfo() 21 | 22 | 23 | def get_order_info(params): 24 | """ 25 | 指定のpaymentIdの注文情報を取得する 26 | 27 | Parameters 28 | ---------- 29 | params : dict 30 | APIGatewayのPOSTパラメータ 31 | paymentIdが渡される 32 | 33 | Returns 34 | ------- 35 | payment_info:dict 36 | 注文情報 37 | """ 38 | 39 | payment_id = params['paymentId'] 40 | payment_info = payment_order_table_controller.get_item(payment_id) 41 | logger.info(payment_info) 42 | if 'transactionId' in payment_info and payment_info['transactionId'] != 0: 43 | logger.error("[payment_id: %s] は会計済みの注文です。", payment_id) 44 | raise Exception 45 | 46 | return payment_info 47 | 48 | 49 | def lambda_handler(event, context): 50 | """ 51 | 会計IDをもとに注文情報を取得し、返却する 52 | Parameters 53 | ---------- 54 | event : dict 55 | フロントより渡されたパラメータ 56 | context : dict 57 | コンテキスト内容。 58 | Returns 59 | ------- 60 | payment_info : dict 61 | 注文情報 62 | """ 63 | # パラメータログ 64 | logger.info(event) 65 | if event['queryStringParameters'] is None: 66 | error_msg_display = common_const.const.MSG_ERROR_NOPARAM 67 | return utils.create_error_response(error_msg_display, 400) 68 | 69 | req_params = event['queryStringParameters'] 70 | param_checker = TableOrderParamCheck(req_params) 71 | 72 | if error_msg := param_checker.check_api_order_info(): 73 | error_msg_disp = ('\n').join(error_msg) 74 | logger.error(error_msg_disp) 75 | return utils.create_error_response(error_msg_disp, status=400) # noqa: E501 76 | 77 | try: 78 | payment_info = get_order_info(req_params) 79 | except Exception as e: 80 | logger.error('Occur Exception: %s', e) 81 | return utils.create_error_response('ERROR') 82 | 83 | return utils.create_success_response( 84 | json.dumps(payment_info, default=utils.decimal_to_int, 85 | ensure_ascii=False)) 86 | -------------------------------------------------------------------------------- /backend/APP/order_info_get/requirements.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/line/line-api-use-case-table-order/40f420b8e6e4f1a90ef65c3fd26b9ef1c0555ef9/backend/APP/order_info_get/requirements.txt -------------------------------------------------------------------------------- /backend/APP/order_put/order_put.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import json 3 | import os 4 | import uuid 5 | import datetime 6 | from decimal import Decimal 7 | from dateutil.tz import gettz 8 | from botocore.exceptions import ClientError 9 | from common import (common_const, utils, line) 10 | 11 | from validation.table_order_param_check import TableOrderParamCheck 12 | from table_order.table_order_item_list import TableOrderItemList # noqa501 13 | from table_order.table_order_payment_order_info import TableOrderPaymentOrderInfo # noqa501 14 | 15 | 16 | # 環境変数の取得 17 | LOGGER_LEVEL = os.environ.get("LOGGER_LEVEL") 18 | LIFF_CHANNEL_ID = os.getenv('LIFF_CHANNEL_ID', None) 19 | # ログ出力の設定 20 | logger = logging.getLogger() 21 | if LOGGER_LEVEL == 'DEBUG': 22 | logger.setLevel(logging.DEBUG) 23 | else: 24 | logger.setLevel(logging.INFO) 25 | 26 | # テーブル操作クラスの初期化 27 | item_list_table_controller = TableOrderItemList() 28 | payment_order_table_controller = TableOrderPaymentOrderInfo() 29 | 30 | # 定数の定義 31 | DISCOUNT_BY_PRICE = 1 32 | DISCOUNT_BY_PERCENTAGE = 2 33 | 34 | 35 | def create_payment_info(params, now): 36 | """ 37 | 新規の注文情報を登録する 38 | 39 | Parameters 40 | ---------- 41 | params : dict 42 | postで送られてきたbodyの中身 43 | now : string 44 | 現在時刻 [yyyy-m-d H:m:s] 45 | 46 | Returns 47 | ------- 48 | payment_id:str 49 | 新規発行したpaymentId 50 | """ 51 | # DBより商品情報を取得し、登録用データを作成する 52 | put_order_items = get_order_item_info(params['item']) 53 | payment_id = str(uuid.uuid4()) 54 | payment_info = { 55 | 'paymentId': payment_id, 56 | 'userId': params['userId'], 57 | 'transactionId': 0, 58 | 'order': [ 59 | { 60 | 'orderId': 1, 61 | 'item': put_order_items, 62 | 'tableId': params['tableId'], 63 | 'cancel': False, 64 | 'deleteReason': '', 65 | 'orderDateTime': now 66 | } 67 | ] 68 | } 69 | calc_amount(payment_info) 70 | try: 71 | payment_order_table_controller.put_item(payment_info) 72 | except ClientError as e: 73 | if e.response['Error']['Code'] == 'ConditionalCheckFailedException': 74 | logger.error("ID[%s]は重複しています。", payment_id) 75 | payment_info['paymentId'] = str(uuid.uuid4()) 76 | payment_order_table_controller.put_item( 77 | payment_info) 78 | raise 79 | 80 | return payment_info['paymentId'] 81 | 82 | 83 | def calc_amount(payment_info): 84 | """ 85 | 合計金額を算出する 86 | Parameters 87 | ---------- 88 | payment_info : dict 89 | 新規に追加する注文情報 90 | Returns 91 | ------- 92 | amount : Decimal 93 | 合計金額 94 | """ 95 | amount = 0 96 | for order in payment_info['order']: 97 | for item in order['item']: 98 | price = item['price'] 99 | if item['discountWay'] == DISCOUNT_BY_PRICE: 100 | price = price - item['discountRate'] 101 | elif item['discountWay'] == DISCOUNT_BY_PERCENTAGE: 102 | price = float(price) * (1 - float(item['discountRate']) * 0.01) 103 | amount = amount + float(price) * float(item['orderNum']) 104 | 105 | amount = Decimal(amount) 106 | payment_info['amount'] = amount 107 | return amount 108 | 109 | 110 | def get_item_info_item_id(item_id, order_num, item_info_list): 111 | """ 112 | itemIdをもとに商品情報を取得し、データを登録用に成形 113 | 114 | Parameters 115 | ---------- 116 | item_id: string 117 | 注文する商品ID 118 | order_num: int 119 | 注文数量 120 | item_info_list : list 121 | 1カテゴリの商品情報 122 | 123 | Returns 124 | ------- 125 | order_item:dict 126 | 注文商品情報 127 | """ 128 | # masterデータをfor文にして注文itemIdと合致する場合、登録用データを作成する 129 | for master_item in item_info_list['items']: 130 | # マスタデータと注文データのitemIdが合致したらデータセット 131 | if master_item['itemId'] == item_id: 132 | order_item = { 133 | 'itemId': master_item['itemId'], 134 | 'itemName': master_item['itemName'], 135 | 'orderNum': order_num, 136 | 'price': master_item['price'], 137 | 'discountRate': master_item['discountRate'], 138 | 'discountWay': master_item['discountWay'], 139 | 'imageUrl': master_item['imageUrl'], 140 | } 141 | return order_item 142 | 143 | 144 | def get_order_item_info(item): 145 | """ 146 | categoryIdとitemIdを元に注文登録用の商品情報を取得する 147 | 148 | Parameters 149 | ---------- 150 | item : list 151 | postで送られてきたbodyの中身 152 | 153 | Returns 154 | ------- 155 | put_order_items:dict 156 | 注文商品情報 157 | """ 158 | # カテゴリーID種類数だけDBに問い合わせるため、item内をカテゴリーIDで昇順にする 159 | order_items = sorted(item, key=lambda x: x['categoryId']) 160 | category_id = None 161 | item_info_list = [] # DBから取得したマスタデータ 162 | put_order_items = [] # DBに登録する商品リスト 163 | for item in order_items: 164 | if category_id is None: 165 | category_id = item['categoryId'] 166 | item_info_list = item_list_table_controller.get_item( 167 | category_id) 168 | elif category_id != item['categoryId']: 169 | item_info_list = item_list_table_controller.get_item( 170 | item['categoryId']) 171 | put_order_items.append( 172 | get_item_info_item_id( 173 | item['itemId'], item['orderNum'], item_info_list)) 174 | 175 | return put_order_items 176 | 177 | 178 | def update_payment_info(params, now): 179 | """ 180 | 指定のpaymentIdの注文情報を更新する 181 | 182 | Parameters 183 | ---------- 184 | params : dict 185 | postで送られてきたbodyの中身 186 | now : string 187 | 現在時刻 [yyyy-m-d H:m:s] の形式 188 | 189 | Returns 190 | ------- 191 | payment_id:int 192 | 更新したドキュメントのpaymentId 193 | """ 194 | # DBより商品情報を取得し、登録データを作成する 195 | put_order_items = get_order_item_info(params['item']) 196 | payment_id = params['paymentId'] 197 | payment_info = payment_order_table_controller.get_item( 198 | payment_id) 199 | 200 | order_id = len(payment_info['order']) + 1 201 | order = { 202 | 'orderId': order_id, 203 | 'item': put_order_items, 204 | 'orderDateTime': now, 205 | 'paymentDeleteFlg': False, 206 | 'tableId': 5, 207 | 'cancel': False, 208 | 'deleteReason': '' 209 | } 210 | payment_info['order'].append(order) 211 | 212 | calc_amount(payment_info) 213 | 214 | logger.debug('modifiedItem %s', payment_info['order']) 215 | 216 | try: 217 | payment_order_table_controller.update_order( 218 | payment_id, params['userId'], 219 | payment_info['order'], payment_info['amount'] 220 | ) 221 | except ClientError as e: 222 | if e.response['Error']['Code'] == 'ConditionalCheckFailedException': 223 | logger.error("会計済みか、ユーザーIDが誤っています。[payment_id: %s, userId: %s]", 224 | payment_id, params['userId']) 225 | raise 226 | 227 | return payment_id 228 | 229 | 230 | def put_order(params): 231 | """ 232 | 商品情報一覧の取得 233 | 234 | Parameters 235 | ---------- 236 | params : dict 237 | APIGatewayのGETパラメータ 238 | 239 | Returns 240 | ------- 241 | paymentId:str 242 | paymentId(create_payment_infoのreturn値) 243 | """ 244 | now = datetime.datetime.now( 245 | gettz('Asia/Tokyo')).strftime('%Y/%m/%d %H:%M:%S') 246 | 247 | if 'paymentId' in params and params['paymentId']: 248 | return update_payment_info(params, now) 249 | 250 | return create_payment_info(params, now) 251 | 252 | 253 | def lambda_handler(event, context): 254 | """ 255 | 注文情報を登録する 256 | Parameters 257 | ---------- 258 | event : dict 259 | フロントより渡されたパラメータ 260 | context : dict 261 | コンテキスト内容 262 | Returns 263 | ------- 264 | payment_id : dict 265 | 会計ID 266 | """ 267 | # パラメータログ 268 | logger.info(event) 269 | if event['body'] is None: 270 | error_msg_display = common_const.const.MSG_ERROR_NOPARAM 271 | return utils.create_error_response(error_msg_display, 400) 272 | 273 | body = json.loads(event['body']) 274 | 275 | # ユーザーID取得 276 | try: 277 | user_profile = line.get_profile(body['idToken'], LIFF_CHANNEL_ID) 278 | if 'error' in user_profile and 'expired' in user_profile['error_description']: # noqa 501 279 | return utils.create_error_response('Forbidden', 403) 280 | else: 281 | body['userId'] = user_profile['sub'] 282 | except Exception: 283 | logger.exception('不正なIDトークンが使用されています') 284 | return utils.create_error_response('Error') 285 | 286 | # パラメータバリデーションチェック 287 | param_checker = TableOrderParamCheck(body) 288 | 289 | if error_msg := param_checker.check_api_order_put(): 290 | error_msg_disp = ('\n').join(error_msg) 291 | logger.error(error_msg_disp) 292 | return utils.create_error_response(error_msg_disp, status=400) # noqa: E501 293 | 294 | try: 295 | payment_id = put_order(body) 296 | except Exception as e: 297 | logger.error('Occur Exception: %s', e) 298 | return utils.create_error_response('ERROR') 299 | return utils.create_success_response( 300 | json.dumps(payment_id, default=utils.decimal_to_int, 301 | ensure_ascii=False)) 302 | -------------------------------------------------------------------------------- /backend/APP/order_put/requirements.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/line/line-api-use-case-table-order/40f420b8e6e4f1a90ef65c3fd26b9ef1c0555ef9/backend/APP/order_put/requirements.txt -------------------------------------------------------------------------------- /backend/APP/payment_confirm/confirm.py: -------------------------------------------------------------------------------- 1 | import json 2 | import logging 3 | import os 4 | import sys 5 | 6 | from table_order import table_order_const 7 | from common import (common_const, line, utils) 8 | from linepay import LinePayApi 9 | from validation.table_order_param_check import TableOrderParamCheck 10 | from common.channel_access_token import ChannelAccessToken 11 | from table_order.table_order_payment_order_info import TableOrderPaymentOrderInfo # noqa501 12 | 13 | 14 | # 環境変数 15 | LOGGER_LEVEL = os.environ.get("LOGGER_LEVEL") 16 | # LINE Pay API 17 | LINE_PAY_CHANNEL_ID = os.environ.get("LINE_PAY_CHANNEL_ID") 18 | LINE_PAY_CHANNEL_SECRET = os.environ.get("LINE_PAY_CHANNEL_SECRET") 19 | if (os.environ.get("LINE_PAY_IS_SANDBOX") == 'True' 20 | or os.environ.get("LINE_PAY_IS_SANDBOX") == 'true'): 21 | LINE_PAY_IS_SANDBOX = True 22 | else: 23 | LINE_PAY_IS_SANDBOX = False 24 | api = LinePayApi(LINE_PAY_CHANNEL_ID, 25 | LINE_PAY_CHANNEL_SECRET, is_sandbox=LINE_PAY_IS_SANDBOX) 26 | # ログ出力の設定 27 | logger = logging.getLogger() 28 | if LOGGER_LEVEL == 'DEBUG': 29 | logger.setLevel(logging.DEBUG) 30 | else: 31 | logger.setLevel(logging.INFO) 32 | # テーブル操作クラスの初期化 33 | payment_order_table_controller = TableOrderPaymentOrderInfo() 34 | channel_access_token_controller = ChannelAccessToken() 35 | 36 | # LINE BOTリソースの宣言 37 | CHANNEL_ID = os.getenv('LINE_CHANNEL_ID', None) 38 | if CHANNEL_ID is None: 39 | logger.error('Specify CHANNEL_ID as environment variable.') 40 | sys.exit(1) 41 | 42 | 43 | def send_messages(body): 44 | """ 45 | OAにメッセージを送信する 46 | Parameters 47 | ---------- 48 | body:dict 49 | 該当ユーザーの支払情報 50 | Returns 51 | ------- 52 | なし 53 | """ 54 | flex_obj = table_order_const.const.FLEX_COUPON 55 | # DBより短期チャネルアクセストークンを取得 56 | channel_access_token = channel_access_token_controller.get_item(CHANNEL_ID) 57 | if channel_access_token is None: 58 | logger.error( 59 | 'CHANNEL_ACCESS_TOKEN in Specified CHANNEL_ID: %s is not exist.', 60 | CHANNEL_ID) 61 | else: 62 | line.send_push_message( 63 | channel_access_token['channelAccessToken'], flex_obj, body['userId']) 64 | 65 | 66 | def lambda_handler(event, context): 67 | """ 68 | LINE Pay API(confirm)の処理結果を返す 69 | Parameters 70 | ---------- 71 | event : dict 72 | POST時に渡されたパラメータ 73 | context : dict 74 | コンテキスト内容。 75 | Returns 76 | ------- 77 | response : dict 78 | LINE Pay APIの通信結果 79 | """ 80 | logger.info(event) 81 | if event['body'] is None: 82 | error_msg_display = common_const.const.MSG_ERROR_NOPARAM 83 | return utils.create_error_response(error_msg_display, 400) 84 | req_body = json.loads(event['body']) 85 | 86 | # パラメータバリデーションチェック 87 | param_checker = TableOrderParamCheck(req_body) 88 | 89 | if error_msg := param_checker.check_api_payment_confirm(): 90 | error_msg_disp = ('\n').join(error_msg) 91 | logger.error(error_msg_disp) 92 | return utils.create_error_response(error_msg_disp, status=400) # noqa: E501 93 | 94 | payment_id = req_body['paymentId'] 95 | transaction_id = int(req_body['transactionId']) 96 | 97 | try: 98 | # 注文履歴から決済金額を取得 99 | payment_info = payment_order_table_controller.get_item( 100 | payment_id) 101 | amount = float(payment_info['amount']) 102 | currency = 'JPY' 103 | # 会計テーブルを更新 104 | payment_order_table_controller.update_payment_info( 105 | payment_id, transaction_id) 106 | # LINE PayAPI予約処理 107 | try: 108 | linepay_api_response = api.confirm( 109 | transaction_id, amount, currency) 110 | res_body = json.dumps(linepay_api_response) 111 | except Exception as e: 112 | # LINE Pay側でエラーが発生した場合は会計テーブルを戻す 113 | logger.error('Occur Exception: %s', e) 114 | transaction_id = 0 115 | payment_order_table_controller.update_payment_info( 116 | payment_id, transaction_id) 117 | return utils.create_error_response("Error") 118 | # プッシュメッセージ送信 119 | send_messages(payment_info) 120 | 121 | except Exception as e: 122 | if transaction_id is not None and transaction_id == 0: 123 | logger.critical( 124 | 'payment_id: %s could not update, please update transaction_id = 0 manually and confirm the payment', # noqa 501 125 | payment_id) 126 | else: 127 | logger.error('Occur Exception: %s', e) 128 | return utils.create_error_response("Error") 129 | 130 | return utils.create_success_response(res_body) 131 | -------------------------------------------------------------------------------- /backend/APP/payment_confirm/requirements.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/line/line-api-use-case-table-order/40f420b8e6e4f1a90ef65c3fd26b9ef1c0555ef9/backend/APP/payment_confirm/requirements.txt -------------------------------------------------------------------------------- /backend/APP/payment_confirm_nolinepay/confirm_nolinepay.py: -------------------------------------------------------------------------------- 1 | import json 2 | import os 3 | import logging 4 | 5 | from common import (common_const, utils) 6 | from validation.table_order_param_check import TableOrderParamCheck 7 | from table_order.table_order_payment_order_info import TableOrderPaymentOrderInfo # noqa501 8 | 9 | 10 | # 環境変数 11 | LOGGER_LEVEL = os.environ.get("LOGGER_LEVEL") 12 | # ログ出力の設定 13 | logger = logging.getLogger() 14 | if LOGGER_LEVEL == 'DEBUG': 15 | logger.setLevel(logging.DEBUG) 16 | else: 17 | logger.setLevel(logging.INFO) 18 | # テーブル操作クラスの初期化 19 | payment_order_table_controller = TableOrderPaymentOrderInfo() 20 | 21 | 22 | def lambda_handler(event, context): 23 | """ 24 | 該当の会計情報を会計済みにする 25 | Parameters 26 | ---------- 27 | event : dict 28 | POST時に渡されたパラメータ 29 | context : dict 30 | コンテキスト内容 31 | Returns 32 | ------- 33 | なし(共通項目のみ) 34 | """ 35 | # パラメータログ 36 | logger.info(event) 37 | if event['body'] is None: 38 | error_msg_display = common_const.const.MSG_ERROR_NOPARAM 39 | return utils.create_error_response(error_msg_display, 400) 40 | req_body = json.loads(event['body']) 41 | 42 | # パラメータバリデーションチェック 43 | param_checker = TableOrderParamCheck(req_body) 44 | 45 | if error_msg := param_checker.check_api_payment_confirm_nolinepay(): 46 | error_msg_disp = ('\n').join(error_msg) 47 | logger.error(error_msg_disp) 48 | return utils.create_error_response(error_msg_disp, status=400) # noqa: E501 49 | 50 | payment_id = req_body['paymentId'] 51 | transaction_id = 99999999999999 52 | 53 | try: 54 | # データを支払済みに更新する 55 | payment_order_table_controller.update_payment_info( 56 | payment_id, transaction_id) 57 | except Exception as e: 58 | logger.error('Occur Exception: %s', e) 59 | return utils.create_error_response("Error") 60 | 61 | return utils.create_success_response("") 62 | -------------------------------------------------------------------------------- /backend/APP/payment_confirm_nolinepay/requirements.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/line/line-api-use-case-table-order/40f420b8e6e4f1a90ef65c3fd26b9ef1c0555ef9/backend/APP/payment_confirm_nolinepay/requirements.txt -------------------------------------------------------------------------------- /backend/APP/payment_id_get/payment_id_get.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import json 3 | import os 4 | from common import (common_const, utils, line) 5 | from validation.table_order_param_check import TableOrderParamCheck 6 | from table_order.table_order_payment_order_info import TableOrderPaymentOrderInfo # noqa501 7 | 8 | 9 | # 環境変数の取得 10 | LOGGER_LEVEL = os.environ.get("LOGGER_LEVEL") 11 | LIFF_CHANNEL_ID = os.getenv('LIFF_CHANNEL_ID', None) 12 | # ログ出力の設定 13 | logger = logging.getLogger() 14 | if LOGGER_LEVEL == 'DEBUG': 15 | logger.setLevel(logging.DEBUG) 16 | else: 17 | logger.setLevel(logging.INFO) 18 | 19 | # テーブル操作クラスの初期化 20 | payment_order_table_controller = TableOrderPaymentOrderInfo() 21 | 22 | 23 | def get_payment_id(user_id): 24 | """ 25 | userIdからPaymentIdの取得 26 | 27 | Parameters 28 | ---------- 29 | user_id : string 30 | APIGatewayのGETパラメータ 31 | ログインユーザーのuserId 32 | 33 | Returns 34 | ------- 35 | paymentId 36 | 未会計データのpaymentId 37 | """ 38 | payment_info = payment_order_table_controller.query_index_user_id_transaction_id( # noqa 501 39 | user_id, 0) 40 | for payment_data in payment_info: 41 | return payment_data['paymentId'] 42 | 43 | return '' 44 | 45 | 46 | def lambda_handler(event, context): 47 | """ 48 | User IDをもとに会計IDを取得し、返却する 49 | Parameters 50 | ---------- 51 | event : dict 52 | フロントより渡されたパラメータ 53 | context : dict 54 | コンテキスト内容。 55 | Returns 56 | ------- 57 | payment_info : dict 58 | 会計ID情報 59 | """ 60 | # パラメータログ 61 | logger.info(event) 62 | if event['queryStringParameters'] is None: 63 | error_msg_display = common_const.const.MSG_ERROR_NOPARAM 64 | return utils.create_error_response(error_msg_display, 400) 65 | 66 | req_params = event['queryStringParameters'] 67 | 68 | # ユーザーID取得 69 | try: 70 | user_profile = line.get_profile(req_params['idToken'], LIFF_CHANNEL_ID) 71 | if 'error' in user_profile and 'expired' in user_profile['error_description']: # noqa 501 72 | return utils.create_error_response('Forbidden', 403) 73 | else: 74 | req_params['userId'] = user_profile['sub'] 75 | except Exception: 76 | logger.exception('不正なIDトークンが使用されています') 77 | return utils.create_error_response('Error') 78 | 79 | try: 80 | payment_id = get_payment_id(req_params['userId']) 81 | except Exception as e: 82 | logger.error('Occur Exception: %s', e) 83 | return utils.create_error_response('ERROR') 84 | 85 | return utils.create_success_response( 86 | json.dumps(payment_id, default=utils.decimal_to_int, 87 | ensure_ascii=False)) 88 | -------------------------------------------------------------------------------- /backend/APP/payment_id_get/requirements.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/line/line-api-use-case-table-order/40f420b8e6e4f1a90ef65c3fd26b9ef1c0555ef9/backend/APP/payment_id_get/requirements.txt -------------------------------------------------------------------------------- /backend/APP/payment_reserve/requirements.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/line/line-api-use-case-table-order/40f420b8e6e4f1a90ef65c3fd26b9ef1c0555ef9/backend/APP/payment_reserve/requirements.txt -------------------------------------------------------------------------------- /backend/APP/payment_reserve/reserve.py: -------------------------------------------------------------------------------- 1 | import json 2 | import os 3 | import logging 4 | 5 | from linepay import LinePayApi 6 | from common import (common_const, utils, line) 7 | from validation.table_order_param_check import TableOrderParamCheck 8 | from table_order.table_order_payment_order_info import TableOrderPaymentOrderInfo # noqa501 9 | 10 | 11 | # 環境変数 12 | CONFIRM_URL = os.environ.get("CONFIRM_URL") 13 | CANCEL_URL = os.environ.get("CANCEL_URL") 14 | LOGGER_LEVEL = os.environ.get("LOGGER_LEVEL") 15 | LIFF_CHANNEL_ID = os.getenv('LIFF_CHANNEL_ID', None) 16 | # LINE Pay API情報 17 | LINE_PAY_CHANNEL_ID = os.environ.get("LINE_PAY_CHANNEL_ID") 18 | LINE_PAY_CHANNEL_SECRET = os.environ.get("LINE_PAY_CHANNEL_SECRET") 19 | if (os.environ.get("LINE_PAY_IS_SANDBOX") == 'True' 20 | or os.environ.get("LINE_PAY_IS_SANDBOX") == 'true'): 21 | LINE_PAY_IS_SANDBOX = True 22 | else: 23 | LINE_PAY_IS_SANDBOX = False 24 | api = LinePayApi(LINE_PAY_CHANNEL_ID, 25 | LINE_PAY_CHANNEL_SECRET, is_sandbox=LINE_PAY_IS_SANDBOX) 26 | # ログ出力の設定 27 | logger = logging.getLogger() 28 | if LOGGER_LEVEL == 'DEBUG': 29 | logger.setLevel(logging.DEBUG) 30 | else: 31 | logger.setLevel(logging.INFO) 32 | # テーブル操作クラスの初期化 33 | payment_order_table_controller = TableOrderPaymentOrderInfo() 34 | 35 | 36 | def lambda_handler(event, context): 37 | """ 38 | LINE Pay API(reserve)の通信結果を返す 39 | Parameters 40 | ---------- 41 | event : dict 42 | POST時に渡されたパラメータ 43 | context : dict 44 | コンテキスト内容 45 | Returns 46 | ------- 47 | response : dict 48 | LINE Pay APIの通信結果 49 | """ 50 | 51 | logger.info(event) 52 | if event['body'] is None: 53 | error_msg_display = common_const.const.MSG_ERROR_NOPARAM 54 | return utils.create_error_response(error_msg_display, 400) 55 | req_body = json.loads(event['body']) 56 | 57 | # ユーザーID取得 58 | try: 59 | user_profile = line.get_profile(req_body['idToken'], LIFF_CHANNEL_ID) 60 | if 'error' in user_profile and 'expired' in user_profile['error_description']: # noqa 501 61 | return utils.create_error_response('Forbidden', 403) 62 | else: 63 | req_body['userId'] = user_profile['sub'] 64 | except Exception: 65 | logger.exception('不正なIDトークンが使用されています') 66 | return utils.create_error_response('Error') 67 | 68 | # パラメータバリデーションチェック 69 | param_checker = TableOrderParamCheck(req_body) 70 | 71 | if error_msg := param_checker.check_api_payment_reserve(): 72 | error_msg_disp = ('\n').join(error_msg) 73 | logger.error(error_msg_disp) 74 | return utils.create_error_response(error_msg_disp, status=400) # noqa: E501 75 | 76 | payment_id = req_body['paymentId'] 77 | payment_info = payment_order_table_controller.get_item(payment_id) 78 | amount = int(payment_info['amount']) 79 | body = { 80 | 'amount': amount, 81 | 'currency': 'JPY', 82 | 'orderId': payment_id, 83 | 'packages': [{ 84 | 'id': '1', 85 | 'amount': amount, 86 | 'name': 'LINE Use Case Barger', 87 | 'products': [{ 88 | 'name': 'オーダー商品', 89 | 'imageUrl': 'https://media.istockphoto.com/vectors/cash-register-with-a-paper-check-flat-isolated-vector-id1018485968', # noqa:E501 90 | 'quantity': '1', 91 | 'price': amount 92 | } 93 | ] 94 | }], 95 | 'redirectUrls': { 96 | 'confirmUrl': CONFIRM_URL, 97 | 'cancelUrl': CANCEL_URL 98 | }, 99 | 'options': { 100 | 'payment': { 101 | 'capture': 'True' 102 | } 103 | } 104 | } 105 | 106 | try: 107 | linepay_api_response = api.request(body) 108 | # 返却データ 109 | res_body = json.dumps(linepay_api_response) 110 | except Exception as e: 111 | logger.error('Occur Exception: %s', e) 112 | return utils.create_error_response("Error") 113 | else: 114 | return utils.create_success_response(res_body) 115 | -------------------------------------------------------------------------------- /backend/APP/samconfig.toml: -------------------------------------------------------------------------------- 1 | version = 0.1 2 | [default] 3 | [default.deploy] 4 | [default.deploy.parameters] 5 | region = "ap-northeast-1" 6 | capabilities = "CAPABILITY_NAMED_IAM" 7 | parameter_overrides = "Environment=\"sample\"" 8 | 9 | -------------------------------------------------------------------------------- /backend/Layer/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/line/line-api-use-case-table-order/40f420b8e6e4f1a90ef65c3fd26b9ef1c0555ef9/backend/Layer/.DS_Store -------------------------------------------------------------------------------- /backend/Layer/layer/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/line/line-api-use-case-table-order/40f420b8e6e4f1a90ef65c3fd26b9ef1c0555ef9/backend/Layer/layer/.DS_Store -------------------------------------------------------------------------------- /backend/Layer/layer/aws/dynamodb/base.py: -------------------------------------------------------------------------------- 1 | """ 2 | DynamoDB操作用基底モジュール 3 | 4 | """ 5 | import boto3 6 | from boto3.dynamodb.conditions import Key 7 | import logging 8 | from datetime import (datetime, timedelta) 9 | 10 | # ログ出力の設定 11 | logger = logging.getLogger() 12 | logger.setLevel(logging.INFO) 13 | 14 | 15 | class DynamoDB: 16 | """DynamoDB操作用基底クラス""" 17 | __slots__ = ['_db', '_table_name'] 18 | 19 | def __init__(self, table_name): 20 | """初期化メソッド""" 21 | self._table_name = table_name 22 | self._db = boto3.resource('dynamodb') 23 | 24 | def _put_item(self, item): 25 | """ 26 | アイテムを登録する 27 | 28 | Parameters 29 | ---------- 30 | item : dict 31 | 登録するアイテム 32 | 33 | Returns 34 | ------- 35 | response : dict 36 | レスポンス情報 37 | 38 | """ 39 | try: 40 | response = self._table.put_item( 41 | Item=self._replace_data_for_dynamodb(item)) 42 | except Exception as e: 43 | raise e 44 | 45 | return response 46 | 47 | def _update_item(self, key, expression, expression_value, return_value): 48 | """ 49 | アイテムを更新する 50 | 51 | Parameters 52 | ---------- 53 | key : dict 54 | 更新するアイテムのキー 55 | expression : str 56 | 更新の式 57 | expression_value : dict 58 | 更新する値 59 | return_value : str 60 | responseで取得する値 61 | 62 | Returns 63 | ------- 64 | response : dict 65 | レスポンス情報 66 | 67 | """ 68 | try: 69 | response = self._table.update_item(Key=key, 70 | UpdateExpression=expression, 71 | ExpressionAttributeValues=self._replace_data_for_dynamodb( # noqa: E501 72 | expression_value), 73 | ReturnValues=return_value) 74 | except Exception as e: 75 | raise e 76 | 77 | return response 78 | 79 | def _update_item_optional(self, key, update_expression, 80 | condition_expression, expression_attribute_names, 81 | expression_value, return_value): 82 | """ 83 | アイテムを更新する 84 | ※キー以外の更新条件がある場合に対応します 85 | ※ 86 | 87 | Parameters 88 | ---------- 89 | key : dict 90 | 更新するアイテムのキー 91 | update_expression : str 92 | 更新の式 93 | condition_expression : str 94 | 更新条件 95 | expression_attribute_names:dict 96 | プレースホルダー 97 | (予約語に対応するため) 98 | expression_value : dict 99 | 各変数宣言 100 | return_value : str 101 | responseで取得する値 102 | 103 | Returns 104 | ------- 105 | response : dict 106 | レスポンス情報 107 | 108 | """ 109 | try: 110 | response = self._table.update_item( 111 | Key=key, 112 | UpdateExpression=update_expression, 113 | ConditionExpression=condition_expression, 114 | ExpressionAttributeNames=expression_attribute_names, # noqa 501 115 | ExpressionAttributeValues=self._replace_data_for_dynamodb( 116 | expression_value), 117 | ReturnValues=return_value, 118 | ) 119 | except Exception as e: 120 | raise e 121 | 122 | return response 123 | 124 | def _delete_item(self, key): 125 | """ 126 | アイテムを削除する 127 | 128 | Parameters 129 | ---------- 130 | key : dict 131 | 削除するアイテムのキー 132 | 133 | Returns 134 | ------- 135 | response : dict 136 | レスポンス情報 137 | 138 | """ 139 | try: 140 | response = self._table.delete_item(Key=key) 141 | except Exception as e: 142 | raise e 143 | 144 | return response 145 | 146 | def _get_item(self, key): 147 | """ 148 | アイテムを取得する 149 | 150 | Parameters 151 | ---------- 152 | key : dict 153 | 取得するアイテムのキー 154 | 155 | Returns 156 | ------- 157 | response : dict 158 | レスポンス情報 159 | 160 | """ 161 | try: 162 | response = self._table.get_item(Key=key) 163 | except Exception as e: 164 | raise e 165 | 166 | return response.get('Item', {}) 167 | 168 | def _query(self, key, value): 169 | """ 170 | queryメソッドを使用してアイテムを取得する 171 | 172 | Parameters 173 | ---------- 174 | key : dict 175 | 取得するアイテムのキー 176 | 177 | Returns 178 | ------- 179 | items : list 180 | 対象アイテムのリスト 181 | 182 | """ 183 | try: 184 | response = self._table.query( 185 | KeyConditionExpression=Key(key).eq(value) 186 | ) 187 | except Exception as e: 188 | raise e 189 | 190 | return response['Items'] 191 | 192 | def _query_index(self, index, expression, expression_value): 193 | """ 194 | indexからアイテムを取得する 195 | 196 | Parameters 197 | ---------- 198 | index : str 199 | index名 200 | expression : str 201 | 検索対象の式 202 | expression_value : dict 203 | expression内で使用する変数名と値 204 | 205 | Returns 206 | ------- 207 | items : list 208 | 検索結果 209 | 210 | """ 211 | try: 212 | response = self._table.query( 213 | IndexName=index, 214 | KeyConditionExpression=expression, 215 | ExpressionAttributeValues=self._replace_data_for_dynamodb( 216 | expression_value), 217 | ) 218 | except Exception as e: 219 | raise e 220 | 221 | return response['Items'] 222 | 223 | def _scan(self, key, value=None): 224 | """ 225 | scanメソッドを使用してデータ取得 226 | 227 | Parameters 228 | ---------- 229 | key : str 230 | キー名 231 | value : object, optional 232 | 検索する値, by default None 233 | 234 | Returns 235 | ------- 236 | items : list 237 | 対象アイテムのリスト 238 | 239 | 240 | """ 241 | scan_kwargs = {} 242 | if value: 243 | scan_kwargs['FilterExpression'] = Key(key).eq(value) 244 | 245 | try: 246 | response = self._table.scan(**scan_kwargs) 247 | except Exception as e: 248 | raise e 249 | 250 | return response['Items'] 251 | 252 | def _get_table_size(self): 253 | """ 254 | アイテム数を取得する 255 | 256 | Returns 257 | ------- 258 | count : int 259 | テーブルのアイテム数 260 | 261 | """ 262 | try: 263 | response = self._table.scan(Select='COUNT') 264 | except Exception as e: 265 | raise e 266 | 267 | return response.get('Count', 0) 268 | 269 | def _replace_data_for_dynamodb(self, value: dict): 270 | return value 271 | -------------------------------------------------------------------------------- /backend/Layer/layer/common/channel_access_token.py: -------------------------------------------------------------------------------- 1 | """ 2 | ChannelAccessTokenテーブル操作用モジュール 3 | 4 | """ 5 | import os 6 | from datetime import datetime 7 | from dateutil.tz import gettz 8 | 9 | from aws.dynamodb.base import DynamoDB 10 | 11 | 12 | class ChannelAccessToken(DynamoDB): 13 | """ChannelAccessToken操作用クラス""" 14 | __slots__ = ['_table'] 15 | 16 | def __init__(self): 17 | """初期化メソッド""" 18 | table_name = os.environ.get('CHANNEL_ACCESS_TOKEN_DB') 19 | super().__init__(table_name) 20 | self._table = self._db.Table(table_name) 21 | 22 | def get_item(self, channel_id): 23 | """ 24 | channelIdからアイテムを取得する 25 | 26 | Parameters 27 | ---------- 28 | channel_id : str 29 | チャネルID 30 | 31 | Returns 32 | ------- 33 | item : dict 34 | チャネルの情報 35 | 36 | """ 37 | key = {'channelId': channel_id} 38 | 39 | try: 40 | item = self._get_item(key) 41 | except Exception as e: 42 | raise e 43 | return item 44 | 45 | def update_item(self, channel_id, channel_access_token, limit_date): 46 | """ 47 | 短期チャネルアクセストークンと期限日を更新する 48 | 49 | Parameters 50 | ---------- 51 | channel_id : str 52 | LINE公式アカウント(Messageing API or MINIアプリ)のチャネルID 53 | channel_access_token : str 54 | チャネルアクセストークン 55 | limit_date : str 56 | 短期チャネルアクセストークンの期限日 57 | 58 | Returns 59 | ------- 60 | response : dict 61 | レスポンス情報 62 | 63 | """ 64 | key = {'channelId': channel_id} 65 | expression = "set channelAccessToken = :channel_access_token, \ 66 | limitDate = :limit_date, updatedTime=:updated_time" 67 | expression_value = { 68 | ':channel_access_token': channel_access_token, 69 | ':limit_date': limit_date, 70 | ':updated_time': datetime.now( 71 | gettz('Asia/Tokyo')).strftime("%Y/%m/%d %H:%M:%S") 72 | } 73 | return_value = "UPDATED_NEW" 74 | 75 | try: 76 | response = self._update_item(key, expression, 77 | expression_value, return_value) 78 | except Exception as e: 79 | raise e 80 | return response 81 | 82 | def scan(self, channel_id=''): 83 | """ 84 | scanメソッドを使用してデータ取得 85 | 86 | Parameters 87 | ---------- 88 | channel_id : str 89 | LINE公式アカウント(Messageing API or MINIアプリ)のチャネルID 90 | 91 | Returns 92 | ------- 93 | items : list 94 | 取得したアイテムのリスト 95 | 96 | """ 97 | 98 | key = 'channelId' 99 | 100 | try: 101 | items = self._scan(key, channel_id) 102 | except Exception as e: 103 | raise e 104 | return items 105 | -------------------------------------------------------------------------------- /backend/Layer/layer/common/const.py: -------------------------------------------------------------------------------- 1 | """ 2 | Constant types in Python. 3 | 定数上書きチェック用 4 | """ 5 | import sys 6 | 7 | 8 | class Const: 9 | class ConstError(TypeError): 10 | pass 11 | 12 | def __setattr__(self, name, value): 13 | if name in self.__dict__: 14 | raise self.ConstError("Can't rebind const (%s)" % name) 15 | self.__dict__[name] = value 16 | 17 | 18 | sys.modules[__name__] = Const() 19 | -------------------------------------------------------------------------------- /backend/Layer/layer/common/line.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import json 3 | import requests 4 | import json 5 | from linebot import LineBotApi 6 | from linebot.models import FlexSendMessage 7 | from linebot.exceptions import ( 8 | LineBotApiError, InvalidSignatureError) 9 | from requests.models import Response 10 | 11 | from common import common_const 12 | 13 | # ログ出力の設定 14 | logger = logging.getLogger() 15 | logger.setLevel(logging.INFO) 16 | 17 | 18 | def send_push_message(channel_access_token, flex_obj, user_id): 19 | """ 20 | プッシュメッセージ送信処理 21 | 22 | Parameters 23 | ---------- 24 | channel_access_token:str 25 | 短期チャネルアクセストークン 26 | flex_obj:dict 27 | メッセージ情報 28 | user_id:str 29 | 送信先のユーザーI 30 | Returns 31 | ------- 32 | response:dict 33 | レスポンス情報 34 | """ 35 | try: 36 | line_bot_api = LineBotApi( 37 | channel_access_token) 38 | # flexdictを生成する 39 | flex_obj = FlexSendMessage.new_from_json_dict(flex_obj) 40 | user_id = user_id 41 | response = line_bot_api.push_message(user_id, flex_obj) 42 | except LineBotApiError as e: 43 | logger.error( 44 | 'Got exception from LINE Messaging API: %s\n' % e.message) 45 | for m in e.error.details: 46 | logger.error(' %s: %s' % (m.property, m.message)) 47 | raise Exception 48 | except InvalidSignatureError as e: 49 | logger.error('Occur Exception: %s', e) 50 | raise Exception 51 | 52 | return response 53 | 54 | 55 | def get_profile(id_token, channel_id): 56 | """ 57 | LINEユーザー情報取得処理 58 | 59 | Parameters 60 | ---------- 61 | id_token:str 62 | IDトークン 63 | channel_id:dict 64 | 使用アプリのLIFFチャネルID 65 | Returns 66 | ------- 67 | res_body:dict 68 | レスポンス情報 69 | """ 70 | 71 | headers = {'Content-Type': 'application/x-www-form-urlencoded'} 72 | body = { 73 | 'id_token': id_token, 74 | 'client_id': channel_id 75 | } 76 | 77 | response = requests.post( 78 | common_const.const.API_USER_ID_URL, 79 | headers=headers, 80 | data=body 81 | ) 82 | 83 | res_body = json.loads(response.text) 84 | return res_body -------------------------------------------------------------------------------- /backend/Layer/layer/common/utils.py: -------------------------------------------------------------------------------- 1 | """ 2 | 共通関数 3 | """ 4 | from decimal import Decimal 5 | from datetime import (datetime, timedelta) 6 | import decimal 7 | import os 8 | 9 | from common import common_const 10 | 11 | 12 | def create_response(status_code, body): 13 | """ 14 | フロントに返却するデータを作成する 15 | 16 | Parameters 17 | ---------- 18 | status_code : int 19 | フロントに返却するステータスコード 20 | body:dict,str 21 | フロントに返却するbodyに格納するデータ 22 | Returns 23 | ------- 24 | response : dict 25 | フロントに返却するデータ 26 | """ 27 | response = { 28 | 'statusCode': status_code, 29 | 'headers': {"Access-Control-Allow-Origin": "*"}, 30 | 'body': body 31 | } 32 | return response 33 | 34 | 35 | def create_error_response(body, status=500): 36 | """ 37 | エラー発生時にフロントに返却するデータを作成する 38 | 39 | Parameters 40 | ---------- 41 | body : dict,str 42 | フロントに返却するbodyに格納するデータ 43 | status:int 44 | フロントに返却するステータスコード 45 | Returns 46 | ------- 47 | create_response:dict 48 | フロントに返却するデータ 49 | """ 50 | return create_response(status, body) 51 | 52 | 53 | def create_success_response(body): 54 | """ 55 | 正常終了時にフロントに返却するデータを作成する 56 | 57 | Parameters 58 | ---------- 59 | body : dict,str 60 | フロントに返却するbodyに格納するデータ 61 | Returns 62 | ------- 63 | create_response:dict 64 | フロントに返却するデータ 65 | """ 66 | return create_response(200, body) 67 | 68 | 69 | def separate_comma(num): 70 | """ 71 | 数値を3桁毎のカンマ区切りにする 72 | 73 | Parameters 74 | ---------- 75 | num : int 76 | カンマ区切りにする数値 77 | 78 | Returns 79 | ------- 80 | result : str 81 | カンマ区切りにした文字列 82 | """ 83 | return '{:,}'.format(num) 84 | 85 | 86 | def decimal_to_int(obj): 87 | """ 88 | Decimal型をint型に変換する。 89 | json形式に変換する際にDecimal型でエラーが出るため作成。 90 | 主にDynamoDBの数値データに対して使用する。 91 | 92 | Parameters 93 | ---------- 94 | obj : obj 95 | Decimal型の可能性があるオブジェクト 96 | 97 | Returns 98 | ------- 99 | int, other 100 | Decimal型の場合int型で返す。 101 | その他の型の場合そのまま返す。 102 | """ 103 | if isinstance(obj, Decimal): 104 | return int(obj) 105 | 106 | 107 | def float_to_int(obj): 108 | """ 109 | float型をint型に変換する。 110 | DynamoDB登録時にfloat型でエラーが出るため作成。 111 | 112 | Parameters 113 | ---------- 114 | obj : obj 115 | float型の可能性があるオブジェクト 116 | 117 | Returns 118 | ------- 119 | int, other 120 | float型の場合int型で返す。 121 | その他の型の場合そのまま返す。 122 | """ 123 | if isinstance(obj, float): 124 | return int(obj) 125 | 126 | 127 | def format_date(date, before_format, after_format): 128 | """ 129 | 指定されたstr型の日付データのフォーマットを変換し、str型で返す 130 | Parameters 131 | date:str 132 | フォーマット変換したいstr型の日付データ 133 | before_format:str 134 | 変換前の日付フォーマット 135 | after_format:str 136 | 変換した日付フォーマット 137 | Returns 138 | ------- 139 | date 140 | 生成したID 141 | """ 142 | before_date = datetime.strptime(date, before_format) 143 | formated_date = before_date.strftime(after_format) 144 | 145 | return formated_date 146 | 147 | 148 | def get_time_interval(time1, time2): 149 | """ 150 | 時間間隔を算出する 151 | Parameters 152 | time1:str 153 | フォーマット変換したいstr型の日付データ 154 | time2:str 155 | 変換前の日付フォーマット 156 | Returns 157 | ------- 158 | interval:datetime.timedelta 159 | 算出した時間差 160 | """ 161 | datetime.strptime(time1, '%H:%M') 162 | interval = time1 - time2 163 | 164 | return interval 165 | 166 | 167 | def timedelta_to_HM(td): 168 | """ 169 | timedelta型のデータをminutesに変換 170 | Parameters 171 | td:timedelta 172 | 変換したいデータ 173 | Returns 174 | ------- 175 | date 176 | 生成したID 177 | """ 178 | sec = td.total_seconds() 179 | hours = sec//3600 * 60 180 | minutes = sec % 3600//60 181 | 182 | return hours + minutes 183 | 184 | 185 | def calculate_date_str_difference(date_str, date_difference): 186 | """ 187 | 日付文字列と差分の日数を元に、日付計算を行う 188 | 189 | Parameters 190 | ---------- 191 | date_str : str 192 | yyyy-MM-dd形式の日付 193 | date_difference : int 194 | 日付の差分 195 | 引き算の場合はマイナス値 196 | 197 | Returns 198 | ------- 199 | result_date_str : str 200 | 計算後のyyyy-MM-dd形式の日付 201 | """ 202 | target_date = datetime.strptime(date_str, '%Y-%m-%d') 203 | date_timedelta = timedelta(days=date_difference) 204 | result_date = target_date + date_timedelta 205 | result_date_str = datetime.strftime(result_date, '%Y-%m-%d') 206 | return result_date_str 207 | 208 | 209 | def get_timestamp_after_one_week(date): 210 | """ 211 | 一週間後の日付のタイムスタンプを取得する。 212 | 213 | Parameters 214 | ---------- 215 | date : str 216 | yyyy-MM-dd形式の日付文字列 217 | 218 | Returns 219 | ------- 220 | after_one_week_date_timestamp: decimal 221 | 指定日付の一週間後のタイムスタンプ 222 | DynamoDBに登録するためdecimal型としている 223 | """ 224 | # データ削除期限日を指定 225 | after_one_week_date_utc = datetime.strptime( 226 | date, '%Y-%m-%d') + common_const.const.ONE_WEEK 227 | after_one_week_date_jst = after_one_week_date_utc - \ 228 | common_const.const.JST_UTC_TIMEDELTA 229 | # timestampはfloat型でDynamoDBに投入できないので変換 230 | after_one_week_date_timestamp = decimal.Decimal( 231 | after_one_week_date_jst.timestamp()) 232 | return after_one_week_date_timestamp 233 | 234 | 235 | def get_ttl_time(param_datetime): 236 | """ 237 | DynamoDBのTimeToLive時間を返却 238 | Parameters 239 | ------- 240 | param_datetime: datetime 241 | ttlを設定する基準となる日 242 | Returns 243 | ------- 244 | delete_unixtime : int 245 | データ削除するUNIX時間 246 | 247 | """ 248 | delete_day = int(os.getenv('TTL_DAY', None)) 249 | delete_date_time = param_datetime + timedelta(days=delete_day) 250 | delete_unixtime = int(delete_date_time.timestamp()) 251 | return delete_unixtime -------------------------------------------------------------------------------- /backend/Layer/layer/requirements.txt: -------------------------------------------------------------------------------- 1 | line-bot-sdk==1.17.0 2 | line-pay -------------------------------------------------------------------------------- /backend/Layer/layer/table_order/table_order_const.py: -------------------------------------------------------------------------------- 1 | import os 2 | from common import const 3 | 4 | const.FLEX_COUPON = { 5 | "type": "flex", 6 | "altText": "ご来店ありがとうございました。またのご来店をお待ちしています。次回ご来店時に使用できるクーポンを発行します。", 7 | "contents": { 8 | "type": "bubble", 9 | "header": { 10 | "type": "box", 11 | "layout": "vertical", 12 | "contents": [ 13 | { 14 | "type": "text", 15 | "text": "10%割引クーポン発行", 16 | "size": "sm", 17 | "color": "#36DB34", 18 | "weight": "bold" 19 | }, 20 | { 21 | "type": "text", 22 | "text": "Use Case 居酒屋", 23 | "size": "xxl", 24 | "weight": "bold" 25 | } 26 | ], 27 | "paddingBottom": "2%" 28 | }, 29 | "hero": { 30 | "type": "image", 31 | "url": "https://media.istockphoto.com/vectors/percent-off-sale-and-discount-price-tag-icon-or-sticker-vector-vector-id1194658271?s=2048x2048", 32 | "size": "full", 33 | "aspectRatio": "2:1", 34 | "aspectMode": "cover", 35 | "action": { 36 | "type": "uri", 37 | "label": "Action", 38 | "uri": os.environ.get("LIFF_URL") 39 | } 40 | }, 41 | "body": { 42 | "type": "box", 43 | "layout": "vertical", 44 | "contents": [ 45 | { 46 | "type": "box", 47 | "layout": "vertical", 48 | "contents": [ 49 | { 50 | "type": "text", 51 | "text": "ご利用ありがとうございました。\n\n次回来店時に本メッセージを店員に提示していただければ、会計から10 % 割引させていただきます。", # noqa:E501 52 | "wrap": True, 53 | "size": "sm", 54 | "color": "#767676" 55 | } 56 | ] 57 | } 58 | ], 59 | "paddingTop": "5%" 60 | }, 61 | "footer": { 62 | "type": "box", 63 | "layout": "vertical", 64 | "spacing": "sm", 65 | "contents": [ 66 | { 67 | "type": "spacer", 68 | "size": "md" 69 | } 70 | ], 71 | "flex": 0 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /backend/Layer/layer/table_order/table_order_item_list.py: -------------------------------------------------------------------------------- 1 | """ 2 | TableOrderItemList操作用モジュール 3 | 4 | """ 5 | import os 6 | from aws.dynamodb.base import DynamoDB 7 | 8 | 9 | class TableOrderItemList(DynamoDB): # ()内のクラスを継承する 10 | """TableOrderItemList操作用クラス""" 11 | __slots__ = ['_table'] # インスタンス側で定義されたメンバ以外のメンバは持てなくなる 12 | 13 | def __init__(self): 14 | """初期化メソッド""" 15 | table_name = os.environ.get("ITEM_LIST_DB") 16 | super().__init__(table_name) 17 | # 基底クラスのメソッドを使って初期化:テーブル名のセットとdynamodbリソースの生成 18 | self._table = self._db.Table(table_name) 19 | 20 | def get_item(self, category_id): 21 | """ 22 | データ取得 23 | 24 | Parameters 25 | ---------- 26 | category_id : int 27 | カテゴリーID 28 | 29 | Returns 30 | ------- 31 | item : dict 32 | 商品情報 33 | 34 | """ 35 | key = {'categoryId': category_id} 36 | 37 | try: 38 | item = self._get_item(key) 39 | except Exception as e: 40 | raise e 41 | return item 42 | 43 | def scan(self): 44 | """ 45 | scanメソッドを使用してデータ取得 46 | 47 | Parameters 48 | ---------- 49 | なし 50 | 51 | Returns 52 | ------- 53 | items : list 54 | 商品情報のリスト 55 | 56 | """ 57 | key = 'category_id' 58 | 59 | try: 60 | items = self._scan(key) 61 | except Exception as e: 62 | raise e 63 | return items 64 | -------------------------------------------------------------------------------- /backend/Layer/layer/table_order/table_order_payment_order_info.py: -------------------------------------------------------------------------------- 1 | """ 2 | TableOrderPaymentOrderInfo操作用モジュール 3 | 4 | """ 5 | import os 6 | from datetime import datetime 7 | from dateutil.tz import gettz 8 | 9 | from aws.dynamodb.base import DynamoDB 10 | from common import utils 11 | 12 | 13 | class TableOrderPaymentOrderInfo(DynamoDB): 14 | """TableOrderPaymentOrderInfo操作用クラス""" 15 | __slots__ = ['_table'] 16 | 17 | def __init__(self): 18 | """初期化メソッド""" 19 | table_name = os.environ.get("PAYMENT_ORDER_DB") 20 | super().__init__(table_name) 21 | self._table = self._db.Table(table_name) 22 | 23 | def get_item(self, payment_id): 24 | """ 25 | データ取得 26 | 27 | Parameters 28 | ---------- 29 | payment_id : int 30 | 会計 31 | 32 | Returns 33 | ------- 34 | item : dict 35 | 注文情報 36 | 37 | """ 38 | key = {'paymentId': payment_id} 39 | 40 | try: 41 | item = self._get_item(key) 42 | except Exception as e: 43 | raise e 44 | return item 45 | 46 | def query_index_user_id_transaction_id(self, user_id, transaction_id): # noqa: E501 47 | """ 48 | queryメソッドを使用してstaffId-reservedYearMonth-indexのインデックスからデータ取得 49 | 50 | Parameters 51 | ---------- 52 | user_id : str 53 | ユーザーID 54 | transaction_id : str 55 | LINE PayのtransactionID 56 | 57 | Returns 58 | ------- 59 | items : list 60 | 特定年月の予約情報のリスト 61 | 62 | """ 63 | index = 'userId-index' 64 | expression = 'userId = :user_id AND transactionId = :transaction_id' # noqa: E501 65 | expression_value = { 66 | ':user_id': user_id, 67 | ':transaction_id': transaction_id 68 | } 69 | 70 | try: 71 | items = self._query_index(index, expression, expression_value) 72 | except Exception as e: 73 | raise e 74 | return items 75 | 76 | def put_item(self, order_info): 77 | """ 78 | データ登録 79 | 80 | Parameters 81 | ---------- 82 | order_info:dict 83 | 注文情報 84 | payment_id : int 85 | 会計ID 86 | amount : int 87 | 注文金額 88 | order : dict 89 | 注文情報 90 | user_id : str 91 | ユーザーID 92 | 93 | Returns 94 | ------- 95 | response :dict 96 | DB登録レスポンス情報 97 | 98 | """ 99 | item = { 100 | "paymentId": order_info['paymentId'], 101 | "amount": order_info['amount'], 102 | "order": order_info['order'], 103 | "userId": order_info['userId'], 104 | "transactionId": 0, 105 | 'createdTime': datetime.now( 106 | gettz('Asia/Tokyo')).strftime("%Y/%m/%d %H:%M:%S"), 107 | 'updatedTime': datetime.now( 108 | gettz('Asia/Tokyo')).strftime("%Y/%m/%d %H:%M:%S"), 109 | } 110 | try: 111 | response = self._put_item(item) 112 | except Exception as e: 113 | raise e 114 | return response 115 | 116 | def update_order(self, payment_id, user_id, order, amount): 117 | """ 118 | データ更新 119 | 120 | Parameters 121 | ---------- 122 | order_info:dict 123 | 注文情報 124 | payment_id : int 125 | 会計ID 126 | amount : int 127 | 注文金額 128 | order : dict 129 | 注文情報 130 | user_id : str 131 | ユーザーID 132 | 133 | Returns 134 | ------- 135 | response :dict 136 | DB更新レスポンス情報 137 | 138 | """ 139 | key = {'paymentId': payment_id} 140 | update_expression = ('set #od = :order, ' 141 | 'amount = :amount, ' 142 | 'updatedTime = :updatedTime') 143 | condition_expression = ('transactionId = :transactionId AND ' 144 | 'userId = :uid') 145 | expression_attribute_names = {'#od': 'order'} 146 | expression_value = { 147 | ':order': order, 148 | ':amount': amount, 149 | ':uid': user_id, 150 | ':transactionId': 0, 151 | ':updatedTime': datetime.now( 152 | gettz('Asia/Tokyo')).strftime("%Y/%m/%d %H:%M:%S") 153 | } 154 | return_value = "UPDATED_NEW" 155 | 156 | try: 157 | response = self._update_item_optional( 158 | key, update_expression, condition_expression, 159 | expression_attribute_names, expression_value, return_value) 160 | except Exception as e: 161 | raise e 162 | return response 163 | 164 | def update_payment_info(self, payment_id, transaction_id): 165 | """ 166 | データ更新 167 | 168 | Parameters 169 | ---------- 170 | payment_id:int 171 | 会計ID 172 | transaction_id : str 173 | LINE PayのtransactionId 174 | 175 | Returns 176 | ------- 177 | response :dict 178 | DB更新レスポンス情報 179 | 180 | """ 181 | key = {'paymentId': payment_id} 182 | update_expression = ('set transactionId = :transactionId, ' 183 | 'paidDatetime = :paidDatetime, ' 184 | 'expirationDate = :expirationDate, ' 185 | 'updatedTime = :updatedTime') 186 | now = datetime.now(gettz('Asia/Tokyo')) 187 | datetime_now = str(now) 188 | expression_value = { 189 | ':transactionId': transaction_id, 190 | ':expirationDate': utils.get_ttl_time(now), 191 | ':paidDatetime': datetime_now, 192 | ':updatedTime': datetime.now( 193 | gettz('Asia/Tokyo')).strftime("%Y/%m/%d %H:%M:%S") 194 | } 195 | return_value = "UPDATED_NEW" 196 | 197 | try: 198 | response = self._update_item( 199 | key, update_expression, expression_value, return_value) 200 | except Exception as e: 201 | raise e 202 | return response 203 | -------------------------------------------------------------------------------- /backend/Layer/layer/validation/param_check.py: -------------------------------------------------------------------------------- 1 | 2 | import datetime 3 | 4 | 5 | class ParamCheck(): 6 | """ 7 | パラメータチェックを実施するクラス。 8 | """ 9 | 10 | def __init__(self): 11 | """ 12 | 初期化を実施する。 13 | """ 14 | pass 15 | 16 | def check_required(self, columns, column_name): 17 | """ 18 | 必須チェックを実施する。 19 | 20 | Parameters 21 | ---------- 22 | columns : obj 23 | 必須チェックをする項目 24 | column_name: str 25 | 項目名 26 | 27 | Returns 28 | ------- 29 | str 30 | エラー内容 31 | """ 32 | columns_replaced = str(columns).replace(' ', '') 33 | 34 | if columns is None or not columns_replaced: 35 | return '必須入力エラー:' + column_name 36 | 37 | def check_length(self, columns, column_name, min, max): 38 | """ 39 | 文字数チェックを実施する。 40 | 41 | Parameters 42 | ---------- 43 | columns : obj 44 | 文字数チェックをする項目 45 | column_name: str 46 | 項目名 47 | min : int 48 | 最小桁数 49 | max : int 50 | 最大桁数 51 | 52 | Returns 53 | ------- 54 | str 55 | エラー内容 56 | """ 57 | if type(columns) is int: 58 | columns = str(columns) 59 | 60 | if min and int(min) > len(columns): 61 | return f'文字数エラー(最小文字数[{min}]未満):{column_name}' 62 | 63 | if max and int(max) < len(columns): 64 | return f'文字数エラー(最大文字数[{max}]超過):{column_name}' 65 | 66 | def check_int(self, columns, column_name): 67 | """ 68 | int型チェックを実施する。 69 | 70 | Parameters 71 | ---------- 72 | columns : obj 73 | int型チェックをする項目 74 | column_name: str 75 | 項目名 76 | 77 | Returns 78 | ------- 79 | str 80 | エラー内容 81 | """ 82 | if isinstance(columns, int): 83 | columns_replaced = True 84 | else: 85 | columns_replaced = columns.isnumeric() 86 | 87 | if columns is None or not columns_replaced: 88 | return 'int型チェックエラー:' + column_name 89 | 90 | def check_year_month(self, columns, column_name): 91 | """ 92 | 年月の形式をチェックする。 93 | 94 | Parameters 95 | ---------- 96 | columns : obj 97 | 形式チェックをする項目 98 | column_name: str 99 | 項目名 100 | 101 | Returns 102 | ------- 103 | str 104 | エラー内容 105 | """ 106 | # 日付のハイフンとスラッシュ区切りに対応 107 | columns_replaced = columns.replace('-', '').replace('/', '') 108 | try: 109 | datetime.datetime.strptime(columns_replaced, "%Y%m") 110 | except ValueError: 111 | return f'年月形式エラー : {column_name}({columns})' 112 | 113 | def check_year_month_day(self, columns, column_name): 114 | """ 115 | 年月日の形式をチェックする。 116 | 117 | Parameters 118 | ---------- 119 | columns : obj 120 | 形式チェックをする項目 121 | column_name: str 122 | 項目名 123 | 124 | Returns 125 | ------- 126 | str 127 | エラー内容 128 | """ 129 | # 日付のハイフンとスラッシュ区切りに対応 130 | columns_replaced = columns.replace('-', '').replace('/', '') 131 | try: 132 | datetime.datetime.strptime(columns_replaced, "%Y%m%d") 133 | except ValueError: 134 | return f'年月日形式エラー : {column_name}({columns})' 135 | 136 | def check_time_format(self, columns, column_name, time_format): 137 | """ 138 | 時間の形式をチェックする。 139 | 140 | Parameters 141 | ---------- 142 | columns : obj 143 | 形式チェックをする項目 144 | column_name: str 145 | 項目名 146 | time_format: str 147 | チェックしたいフォーマット 148 | 149 | Returns 150 | ------- 151 | str 152 | エラー内容 153 | """ 154 | # 日付のハイフンとスラッシュ区切りに対応 155 | columns_replaced = columns.replace(':', '') 156 | try: 157 | datetime.datetime.strptime(columns_replaced, time_format) 158 | except ValueError: 159 | return f'時間形式エラー : {column_name}({columns})' 160 | -------------------------------------------------------------------------------- /backend/Layer/layer/validation/table_order_param_check.py: -------------------------------------------------------------------------------- 1 | from validation.param_check import ParamCheck 2 | 3 | 4 | class TableOrderParamCheck(ParamCheck): 5 | def __init__(self, params): 6 | self.category_id = params['categoryId'] if 'categoryId' in params else None # noqa:E501 7 | self.table_id = params['tableId'] if 'tableId' in params else None 8 | self.payment_id = params['paymentId'] if 'paymentId' in params else None # noqa:E501 9 | self.transaction_id = params['transactionId'] if 'transactionId' in params else None # noqa:E501 10 | 11 | self.item = params['item'] if 'item' in params else None # noqa:E501 12 | 13 | self.error_msg = [] 14 | 15 | def check_api_order_put(self): 16 | self.check_table_id() 17 | 18 | self.check_item() 19 | 20 | return self.error_msg 21 | 22 | def check_api_order_info(self): 23 | self.check_payment_id() 24 | 25 | return self.error_msg 26 | 27 | def check_api_payment_reserve(self): 28 | self.check_payment_id() 29 | 30 | return self.error_msg 31 | 32 | def check_api_payment_confirm(self): 33 | self.check_transaction_id() 34 | self.check_payment_id() 35 | 36 | return self.error_msg 37 | 38 | def check_api_payment_confirm_nolinepay(self): 39 | self.check_payment_id() 40 | 41 | return self.error_msg 42 | 43 | def check_table_id(self): 44 | if error := self.check_required(self.table_id, 'tableId'): 45 | self.error_msg.append(error) 46 | return 47 | 48 | if error := self.check_length(self.table_id, 'tableId', 1, None): 49 | self.error_msg.append(error) 50 | 51 | def check_category_id(self): 52 | if error := self.check_required(self.category_id, 'categoryId'): 53 | self.error_msg.append(error) 54 | return 55 | 56 | if error := self.check_length(self.category_id, 'categoryId', 1, None): # noqa:E501 57 | self.error_msg.append(error) 58 | 59 | def check_payment_id(self): 60 | if error := self.check_required(self.payment_id, 'paymentId'): 61 | self.error_msg.append(error) 62 | return 63 | 64 | if error := self.check_length(self.payment_id, 'paymentId', 1, None): # noqa:E501 65 | self.error_msg.append(error) 66 | 67 | def check_transaction_id(self): 68 | if error := self.check_required(self.transaction_id, 'transactionId'): 69 | self.error_msg.append(error) 70 | return 71 | 72 | if error := self.check_length(self.transaction_id, 'transactionId', 1, None): # noqa:E501 73 | self.error_msg.append(error) 74 | 75 | def check_item(self): 76 | def check_category_id(self, category_id): 77 | if error := self.check_required(category_id, 'categoryId'): 78 | self.error_msg.append(error) 79 | return 80 | 81 | def check_item_id(self, item_id): 82 | if error := self.check_required(item_id, 'itemId'): 83 | self.error_msg.append(error) 84 | return 85 | 86 | if error := self.check_length(item_id, 'itemId', 1, None): # noqa:E501 87 | self.error_msg.append(error) 88 | 89 | def check_order_num(self, order_num): 90 | if error := self.check_required(order_num, 'orderNum'): 91 | self.error_msg.append(error) 92 | return 93 | 94 | # itemがあるか確認→ない場合は中身のチェック無し 95 | if error := self.check_required(self.item, 'item'): 96 | self.error_msg.append(error) 97 | return 98 | 99 | # itemの中身をループでチェック 100 | for item_single in self.item: 101 | check_category_id(self, 102 | item_single['categoryId'] 103 | if 'categoryId' in item_single else None) 104 | check_item_id(self, 105 | item_single['itemId'] 106 | if 'itemId' in item_single else None) 107 | check_order_num(self, 108 | item_single['orderNum'] 109 | if 'orderNum' in item_single else None) 110 | -------------------------------------------------------------------------------- /backend/Layer/samconfig.toml: -------------------------------------------------------------------------------- 1 | version = 0.1 2 | [default] 3 | [default.deploy] 4 | [default.deploy.parameters] 5 | region = "ap-northeast-1" 6 | capabilities = "CAPABILITY_IAM" 7 | parameter_overrides = "Environment=\"dev\"" 8 | 9 | -------------------------------------------------------------------------------- /backend/Layer/template.yaml: -------------------------------------------------------------------------------- 1 | # Stack:LINE-UseCase-TableOrder-Layer 2 | AWSTemplateFormatVersion: "2010-09-09" 3 | Transform: AWS::Serverless-2016-10-31 4 | Description: > 5 | LINE-UseCase-Layer-Sample 6 | 7 | # More info about Globals: https://github.com/awslabs/serverless-application-model/blob/master/docs/globals.rst 8 | Globals: 9 | Function: 10 | Timeout: 30 11 | 12 | Parameters: 13 | Environment: 14 | Type: String 15 | AllowedValues: 16 | - dev 17 | - sample 18 | - prod 19 | Default: dev 20 | 21 | Mappings: 22 | EnvironmentMap: 23 | dev: 24 | LayerName: LINE-USECASE-TABLEORDER-LAYER-DEV 25 | prod: 26 | LayerName: LINE-USECASE-TABLEORDER-LAYER-PROD 27 | 28 | Resources: 29 | UseCaseLayerDev: 30 | Type: AWS::Serverless::LayerVersion 31 | Properties: 32 | CompatibleRuntimes: 33 | - python3.8 34 | ContentUri: layer 35 | Description: LambdaLayer 36 | CompatibleRuntimes: 37 | - python3.8 38 | LayerName: !FindInMap [EnvironmentMap, !Ref Environment, LayerName] 39 | Metadata: 40 | BuildMethod: python3.8 41 | 42 | Outputs: 43 | # ServerlessRestApi is an implicit API created out of Events key under Serverless::Function 44 | # Find out more about other implicit resources you can reference within SAM 45 | # https://github.com/awslabs/serverless-application-model/blob/master/docs/internals/generated_resources.rst#api 46 | UseCaseLayerDev: 47 | Description: "UseCaseLayerDev Layer ARN" 48 | Value: !Ref UseCaseLayerDev 49 | UseCaseLayerName: 50 | Description: "UseCaseLayerDev Layer Name" 51 | Value: !FindInMap [EnvironmentMap, !Ref Environment, LayerName] 52 | Export: 53 | Name: TableOrderLayerDev -------------------------------------------------------------------------------- /backend/batch/samconfig.toml: -------------------------------------------------------------------------------- 1 | version = 0.1 2 | [default] 3 | [default.deploy] 4 | [default.deploy.parameters] 5 | region = "ap-northeast-1" 6 | capabilities = "CAPABILITY_NAMED_IAM" 7 | parameter_overrides = "Environment=\"dev\"" 8 | 9 | -------------------------------------------------------------------------------- /backend/batch/template.yaml: -------------------------------------------------------------------------------- 1 | # Stack:LINE-UseCase-TableOrder-Batch 2 | AWSTemplateFormatVersion: "2010-09-09" 3 | Transform: AWS::Serverless-2016-10-31 4 | Description: > 5 | UseCase-ChannelAccessToken-Batch 6 | 7 | # More info about Globals: https://github.com/awslabs/serverless-application-model/blob/master/docs/globals.rst 8 | Globals: 9 | Function: 10 | Timeout: 30 11 | MemorySize: 128 12 | 13 | Parameters: 14 | Environment: 15 | Type: String 16 | AllowedValues: 17 | - dev 18 | - prod 19 | Default: dev 20 | Mappings: 21 | EnvironmentMap: 22 | dev: 23 | LINEChannelAccessTokenDBName: LINEChannelAccessTokenDBTableOrderDev 24 | EventBridgeName: EventBridgeNameTableOrderDev 25 | LayerVersion: Layer LayerVersion 26 | LoggerLevel: DEBUG 27 | prod: 28 | LINEChannelAccessTokenDBName: LINEChannelAccessTokenDBTableOrderProd 29 | EventBridgeName: EventBridgeNameTableOrderProd 30 | LayerVersion: Layer LayerVersion 31 | LoggerLevel: INFO or DEBUG 32 | 33 | Resources: 34 | EventBridge: 35 | Type: AWS::Events::Rule 36 | Properties: 37 | Description: Check&Update for ShortTermAccessToken 38 | # EventBusName: String 39 | # EventPattern: Json 40 | Name: !FindInMap [EnvironmentMap, !Ref Environment, EventBridgeName] 41 | # RoleArn: String 42 | ScheduleExpression: cron(0 0 * * ? *) 43 | State: ENABLED 44 | Targets: 45 | - Arn: !GetAtt PutAccessToken.Arn 46 | Id: TargetFunctionV1 47 | 48 | PutAccessTokenFunctionPermission: 49 | Type: AWS::Lambda::Permission 50 | Properties: 51 | FunctionName: !Ref PutAccessToken 52 | Action: "lambda:InvokeFunction" 53 | Principal: "events.amazonaws.com" 54 | SourceArn: !GetAtt EventBridge.Arn 55 | 56 | LambdaRole: 57 | Type: AWS::IAM::Role 58 | Properties: 59 | AssumeRolePolicyDocument: 60 | Version: "2012-10-17" 61 | Statement: 62 | - Effect: Allow 63 | Action: sts:AssumeRole 64 | Principal: 65 | Service: 66 | - lambda.amazonaws.com 67 | Policies: 68 | - PolicyName: root 69 | PolicyDocument: 70 | Version: 2012-10-17 71 | Statement: 72 | - Effect: Allow 73 | Action: logs:PutLogEvents 74 | Resource: !Sub "arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/TableOrder-*:*:*" 75 | - Effect: Allow 76 | Action: 77 | - logs:CreateLogStream 78 | - dynamodb:Scan 79 | - dynamodb:GetItem 80 | - dynamodb:PutItem 81 | - dynamodb:UpdateItem 82 | Resource: 83 | - !Sub "arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/TableOrder-*:*" 84 | - !GetAtt LINEChannelAccessTokenDB.Arn 85 | - Effect: Allow 86 | Action: 87 | - logs:CreateLogGroup 88 | - sts:AssumeRole 89 | - ssm:GetParameters 90 | Resource: "*" 91 | RoleName: !Sub "${AWS::StackName}-LambdaRole" 92 | 93 | PutAccessToken: 94 | Type: "AWS::Serverless::Function" 95 | Properties: 96 | Handler: update_line_access_token.lambda_handler 97 | Runtime: python3.8 98 | CodeUri: update_line_access_token/ 99 | FunctionName: !Sub TableOrder-PutAccessToken-${Environment} 100 | Description: "" 101 | Layers: 102 | - !Join 103 | - ":" 104 | - - !Sub "arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:layer" 105 | - !ImportValue TableOrderLayerDev 106 | - !FindInMap [EnvironmentMap, !Ref Environment, LayerVersion] 107 | Role: !GetAtt LambdaRole.Arn 108 | Environment: 109 | Variables: 110 | LOGGER_LEVEL: 111 | !FindInMap [EnvironmentMap, !Ref Environment, LoggerLevel] 112 | CHANNEL_ACCESS_TOKEN_DB: !Ref LINEChannelAccessTokenDB 113 | 114 | LINEChannelAccessTokenDB: 115 | Type: "AWS::DynamoDB::Table" 116 | Properties: 117 | AttributeDefinitions: 118 | - AttributeName: "channelId" 119 | AttributeType: S 120 | TableName: 121 | !FindInMap [ 122 | EnvironmentMap, 123 | !Ref Environment, 124 | LINEChannelAccessTokenDBName, 125 | ] 126 | SSESpecification: 127 | SSEEnabled: True 128 | KeySchema: 129 | - AttributeName: "channelId" 130 | KeyType: "HASH" 131 | ProvisionedThroughput: 132 | ReadCapacityUnits: 1 133 | WriteCapacityUnits: 1 134 | 135 | Outputs: 136 | # ServerlessRestApi is an implicit API created out of Events key under Serverless::Function 137 | # Find out more about other implicit resources you can reference within SAM 138 | # https://github.com/awslabs/serverless-application-model/blob/master/docs/internals/ 139 | LambdaLayer: 140 | Description: "Used Layer at Function" 141 | Value: !Join 142 | - ":" 143 | - - !Sub "arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:layer" 144 | - !ImportValue TableOrderLayerDev 145 | LINEChannelAccessTokenDB: 146 | Description: "DynamoDB ARN for LINEChannelAccessToken" 147 | Value: !GetAtt LINEChannelAccessTokenDB.Arn 148 | -------------------------------------------------------------------------------- /backend/batch/update_line_access_token/requirements.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/line/line-api-use-case-table-order/40f420b8e6e4f1a90ef65c3fd26b9ef1c0555ef9/backend/batch/update_line_access_token/requirements.txt -------------------------------------------------------------------------------- /backend/batch/update_line_access_token/update_line_access_token.py: -------------------------------------------------------------------------------- 1 | import os 2 | import logging 3 | import json 4 | import requests 5 | from datetime import (datetime, timedelta) 6 | from dateutil.tz import gettz 7 | 8 | from common import common_const as const 9 | from common.channel_access_token import ChannelAccessToken 10 | 11 | # 環境変数 12 | LOGGER_LEVEL = os.environ.get("LOGGER_LEVEL") 13 | # ログ出力の設定 14 | logger = logging.getLogger() 15 | if LOGGER_LEVEL == 'DEBUG': 16 | logger.setLevel(logging.DEBUG) 17 | else: 18 | logger.setLevel(logging.INFO) 19 | 20 | # テーブル操作クラスの初期化 21 | channel_access_token_table_controller = ChannelAccessToken() 22 | 23 | 24 | def update_limited_channel_access_token(channel_id, channel_access_token): # noqa 501 25 | """ 26 | 指定のチャンネルIDの短期チャネルアクセストークンを更新する 27 | Parameters 28 | table : dynamoDB.Table 29 | dynamoDBのテーブルオブジェクト 30 | key :string 31 | itemDict : dict 32 | 新規アイテム 33 | 34 | Returns 35 | ------- 36 | なし 37 | """ 38 | now = datetime.now(gettz('Asia/Tokyo')) 39 | # 取得から20日を期限とする 40 | limit_date = (now + timedelta(days=20)).strftime('%Y-%m-%d %H:%M:%S%z') 41 | 42 | channel_access_token_table_controller.update_item(channel_id, 43 | channel_access_token, 44 | limit_date) 45 | 46 | 47 | def get_channel_access_token(channel_id, channel_secret): 48 | """ 49 | MINIアプリの短期チャネルアクセストークンを新規で取得する 50 | 51 | Returns 52 | ------- 53 | str 54 | access_token:短期のチャネルアクセストークン 55 | """ 56 | 57 | headers = {'Content-Type': 'application/x-www-form-urlencoded'} 58 | body = { 59 | 'grant_type': 'client_credentials', 60 | 'client_id': channel_id, 61 | 'client_secret': channel_secret 62 | } 63 | 64 | response = requests.post( 65 | const.const.API_ACCESSTOKEN_URL, 66 | headers=headers, 67 | data=body 68 | ) 69 | logger.debug('new_channel_access_token %s', response.text) 70 | res_body = json.loads(response.text) 71 | 72 | return res_body['access_token'] 73 | 74 | 75 | def lambda_handler(event, contexts): 76 | """ 77 | dbの短期チャネルアクセストークンの期限をチェックし更新する 78 | 79 | Returns 80 | ------- 81 | event 82 | channel_access_token:短期チャネルアクセストークン 83 | """ 84 | channel_access_token_info = channel_access_token_table_controller.scan() 85 | for item in channel_access_token_info: 86 | # 途中処理でエラーが発生した場合でも後続処理が走るようにする 87 | try: 88 | if item.get('channelAccessToken'): 89 | limit_date = datetime.strptime( 90 | item['limitDate'], '%Y-%m-%d %H:%M:%S%z') 91 | now = datetime.now(gettz('Asia/Tokyo')) 92 | # 本日以前の場合トークン再取得する 93 | if limit_date < now: 94 | channel_access_token = get_channel_access_token( 95 | item['channelId'], item['channelSecret']) 96 | # DBのチャネルアクセストークンを更新 97 | update_limited_channel_access_token( 98 | item['channelId'], channel_access_token) 99 | logger.info('channelId: %s updated', item['channelId']) 100 | else: 101 | channel_access_token = item['channelAccessToken'] # noqa: E501 102 | # 1度もアクセストークン取得していない場合は新規取得する 103 | else: 104 | channel_access_token = get_channel_access_token( 105 | item['channelId'], item['channelSecret']) 106 | update_limited_channel_access_token( 107 | item['channelId'], channel_access_token) 108 | logger.info('channelId: %s created', item['channelId']) 109 | except Exception as e: 110 | logger.error('Occur Exception: %s', e) 111 | continue 112 | -------------------------------------------------------------------------------- /docs/en/README_en.md: -------------------------------------------------------------------------------- 1 | # Overview 2 | 3 | This is the demo app source code for [Table Order](https://lineapiusecase.com/en/usecase/tableorder.html) provided on the [LINE API Use Case site](https://lineapiusecase.com/en/top.html). By referring to the steps described in this article, you can develop a table order app using the LINE API. 4 | 5 | With the Table Order app, you can display a restaurant's menu on the LINE app and place an order - and make payment. This eliminates the need for stores to have their own hardware for ordering. In addition, you can use the user ID obtained from the LIFF app after payment to send promotional messages via LINE. 6 | 7 | The source code environment introduced on this page uses AWS. 8 | 9 | ※ [日本語ドキュメントはこちらからご確認いただけます。](../../README.md) 10 | 11 | # Libraries 12 | 13 | ## Node.js 14 | 15 | Install Node.js, which will be used for the front-end development, in your local development environment. 16 | *We recommend installing the latest LTS version of v10.13 or higher 17 | 18 | Node.js download site: 19 | 20 | https://nodejs.org/en/download/ 21 | 22 | ## Python 23 | 24 | Install Python version 3.8 or later if it isn't already installed. 25 | 26 | You can check if it's installed by entering this command in Command Prompt or Terminal: 27 | 28 | ``` 29 | python --version 30 | ``` 31 | 32 | If Python is installed, the version will be returned. For example: 33 | 34 | ``` 35 | Python 3.8.3 36 | ``` 37 | 38 | Install Python (3.8 or higher), used for back-end development, in your local development environment if it's not already installed. 39 | 40 | Python installation reference site: 41 | 42 | Windows: https://docs.python.org/3/using/windows.html 43 | Mac: https://docs.python.org/3/using/mac.html 44 | 45 | ## AWS SAM 46 | 47 | *Note: As of May 12, an error may occur when executing the command due to a problem on the AWS side. If an error occurs during execution, delete the corresponding property of the file. 48 | 49 | The AWS Serverless Application Model (AWS SAM) is used to deploy this app. 50 | 51 | See the [official AWS documentation](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-install.html) to register and set up an AWS account, and install the AWS SAM CLI and Docker. 52 | 53 | *Recommended version of SAM CLI is 1.15.0 or later. 54 | *Docker installation is also required, with or without local testing. 55 | 56 | ### Reference points in the official documentation 57 | 58 | Complete these items in the official documentation and proceed to the next step. If you have already installed it, you can skip this section. 59 | 60 | *This document was created in December 2020 and may be inconsistent with the latest official documentation. 61 | 62 | 1. Install AWS SAM CLI 63 | 1. Set up AWS authentication credentials 64 | 1. (Optional) Tutorial: Deploying the Hello World app 65 | 66 | # Getting Started / Tutorial 67 | 68 | This procedure explains how to create a LINE channel, build the backend and frontend, and check the operation, which are necessary for app development. 69 | 70 | See these steps to build your local and production (AWS) environment: 71 | 72 | - **[Create a LINE channel](liff-channel-create.md)** 73 | - **[Build the back-end](back-end-construction.md)** 74 | - **[Build front end production environment (AWS)](front-end-construction.md)** 75 | - **[Build a local front end environment](front-end-development-environment.md)** 76 | *** 77 | - **[Test data input](test-data-charge.md)** 78 | *** 79 | - **[Check operation](validation.md)** 80 | *** 81 | 82 | # License 83 | 84 | All files on Table Order are free to use without conditions. 85 | 86 | Download and clone to start developing apps using LINE API. 87 | 88 | See [LICENSE](LICENSE) for details. (English) 89 | 90 | # How to contribute 91 | 92 | Thank you for taking the time to contribute. LINE API Use Case Table Order isn't different from any other open source projects. You can help by: 93 | 94 | - Filing an issue in [the issue tracker](https://github.com/line/line-api-use-case-table-order/issues) to report bugs and propose new features and improvements. 95 | - Asking a question using [the issue tracker](https://github.com/line/line-api-use-case-table-order/issues). 96 | - Contributing your work by submitting [a pull request](https://github.com/line/line-api-use-case-table-order/pulls). 97 | 98 | When you are sending a pull request, be aware of and accepting the following: 99 | 100 | - Grant [the same license](LICENSE) to the contribution 101 | - Represent the contribution is your own creation 102 | - Not expected to give support for your contribution 103 | -------------------------------------------------------------------------------- /docs/en/front-end-construction.md: -------------------------------------------------------------------------------- 1 | ## Production (AWS) front-end environment construction 2 | 3 | 1. .env file settings 4 | 5 | Set the LIFF ID of the LIFF app in `LIFF_ID` of the front/.env file, the URL of AWS APIGateway in `BASE_URL`, and the stage name of AWS APIGateway in `APIGATEWAY_STAGE`. 6 | 7 | ▼ .env file 8 | ```` 9 | # LIFF ID 10 | LIFF_ID=9999999999-xxxxxxxx 11 | 12 | # AXIOS BASE URL 13 | BASE_URL=https://xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com 14 | 15 | # API Gateway Stage 16 | APIGATEWAY_STAGE=dev 17 | 18 | # Ajax Module (axios or amplify) 19 | AJAX_MODULE=amplify 20 | ```` 21 | 22 | 1. node_modules installation 23 | 24 | If the Node.js dependent package (* node_modules folder) isn't installed in the front project, execute one of these commands directly under the front folder to install node_modules: 25 | ``` 26 | npm install 27 | ``` 28 | or 29 | ``` 30 | yarn install 31 | ``` 32 | 33 | 1. Static build 34 | 35 | Generate a static module that builds the front side and place it in S3. Run one of these commands directly under the front folder: 36 | ``` 37 | npm run build 38 | ``` 39 | or 40 | ``` 41 | yarn run build 42 | ``` 43 | When the build is complete, the front/dist folder will be created. The files in the dist folder are placed in S3. 44 | 45 | 1. Place front-end modules in S3 46 | 47 | Place the module built by Static build (*all dist folder content) in the target S3. 48 | 49 | 50 | [Next page](test-data-charge.md) 51 | 52 | [Back to Table of Contents](README_en.md) 53 | -------------------------------------------------------------------------------- /docs/en/front-end-development-environment.md: -------------------------------------------------------------------------------- 1 | ## Building a local front-end environment 2 | 3 | Front-end development is done in the Nuxt.js project's Single Page Application (SPA). Start the Nuxt development server and static build the production module in your local environment. After downloading the source code, perform these operations in the local environment. 4 | 5 | - .env file settings 6 | 7 | Set the .env file to the value used by the front application. 8 | 9 | ▼ .env file 10 | ``` 11 | # LIFF ID 12 | LIFF_ID=9999999999-xxxxxxxx 13 | 14 | # AXIOS BASE URL 15 | BASE_URL=https://xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com 16 | 17 | # API Gateway Stage 18 | APIGATEWAY_STAGE=dev 19 | 20 | # Ajax Module (axios or amplify) 21 | AJAX_MODULE=amplify 22 | ``` 23 | 24 | - Set `LIFF_ID` to the LIFF ID of the LINE channel's LIFF app. 25 | - Set the URL of AWS APIGateway to `BASE_URL` 26 | - Set the stage name of the AWS APIGateway to `APIGATEWAY_STAGE`. 27 | - Set the module used for Ajax communication (Amplify API is "amplify" / Axios is "axios") in `AJAX_MODULE` 28 | 29 | - node_modules installation 30 | 31 | If the Node.js dependency package (* node_modules folder) isn't installed in the front project, run this command directly under the front folder to install node_modules. 32 | ``` 33 | npm install 34 | ``` 35 | or 36 | ``` 37 | yarn install 38 | ``` 39 | After the installation is complete, the front/`node_modules` folder will be created. 40 | 41 | - Fixing LIFF app endpoint URLs 42 | 43 | Since you're developing on a web server in a local development environment, change the `endpoint URL` of the LIFF app on the LINE channel to this URL (*Change it back to the CloudFront URL after development is complete). 44 | ``` 45 | https://localhost:3000 46 | ``` 47 | 48 | - HTTPS communication of Nuxt development server 49 | 50 | The `endpoint URL` of the LIFF app for the LINE channel can only be set to a URL starting with https. Therefore, you need to use an SSL certificate to make the Nuxt development server use HTTPS communication. Place the SSL certificate (`localhost.key`, `localhost.crt`) in the following location. 51 | ``` 52 | front/cert 53 | ``` 54 | After placing, remove the following comment out (`//`) from `nuxt.config.js`. 55 | ``` 56 | export default { 57 | telemetry: false, 58 | mode: 'spa', 59 | generate: { 60 | dir: './dist' 61 | }, 62 | server: { 63 | port: 3000, 64 | host: '0.0.0.0', 65 | timing: false, 66 | https: { 67 | // key: fs.readFileSync('./cert/localhost.key'), 68 | // cert: fs.readFileSync('./cert/localhost.crt') 69 | } 70 | }, 71 | ``` 72 | [Reference]   73 | An example of creating an SSL certificate (self-signed certificate) 74 | *How to create an SSL certificate using the openssl command 75 | ``` 76 | # Private key file generation 77 | openssl genrsa -out localhost.key 2048 78 | # Generate certificate signing request file 79 | openssl req -new -key localhost.key -out localhost.csr -subj '/C=JP/ST=Tokyo/L=Tokyo/O=Example Ltd./OU=Web/CN=localhost' 80 | # Certificate generation 81 | openssl x509 -in localhost.csr -days 3650 -req -signkey localhost.key -out localhost.crt 82 | ``` 83 | 84 | - Start the Nuxt development server 85 | 86 | Development in the local environment is done by starting the Nuxt development server. Start the Nuxt development server by executing the following command directly under the front folder. You should now be able to access `https://localhost:3000`. If you have already set "Fixing LIFF app endpoint URLs" above, you can access your local environment with the LIFF URL of your LIFF app (e.g. `https://liff.line.me/9999999999-xxxxxxxx`) for development. 87 | ``` 88 | npm run dev 89 | ``` 90 | or 91 | ``` 92 | yarn run dev 93 | ``` 94 | 95 | 96 | 97 | [Next page](test-data-charge.md) 98 | 99 | [Back to Table of Contents](README_en.md) 100 | -------------------------------------------------------------------------------- /docs/en/liff-channel-create.md: -------------------------------------------------------------------------------- 1 | # Creating a LINE channel 2 | 3 | A channel is a communication path between a system created by a developer and the LINE Platform. 4 | The following LINE channels are required for this app. Follow these instructions to create them. 5 | 6 | 1. LINE channel for Messaging API 7 | ・Channel creation (Japanese only): https://developers.line.biz/ja/docs/clova-extensions-kit/create-messaging-api-channel-t4/ 8 | ・About Messaging API: https://lineapiusecase.com/en/api/msgapi.html 9 | 10 | 1. LINE channel for LIFF 11 | ・Channel creation: https://developers.line.biz/en/docs/liff/registering-liff-apps/ 12 | ・About LIFF: https://lineapiusecase.com/en/api/miniliff.html 13 | 14 | 1. Channel for LINE Pay 15 | ・Channel creation: https://pay.line.me/tw/developers/techsupport/sandbox/testflow?locale=en_US 16 | ・About LINE Pay: https://lineapiusecase.com/en/api/pay.html 17 | 18 | ## 1. Create a LINE account 19 | 20 | *Skip this step if you already have a LINE account. 21 | 22 | 1. Download the LINE app from the download link below and create a LINE account 23 | https://line.me/en/ 24 | 25 | ## 2. Log in to LINE Developers 26 | 27 | LINE Developers is a site for developers to create LINE Official Accounts and LIFF apps required for this app. You can develop apps by logging in to the LINE Developers site using your LINE account. 28 | 29 | 1. Click this link and go to the login screen. 30 | https://developers.line.biz/en/ 31 | 1. Log in with your LINE account. 32 | 1. Enter the email address and password set for your LINE account, or use the QR code login to scan the QR code from another device to log in. 33 | *If you want to log in with your email address, you'll need to set it up separately in the LINE app. For more information, see: 34 | https://appllio.com/line-mail-address-settings 35 | 36 | ## 3. Create a provider 37 | 38 | A provider is a team, company, or individual that manages multiple channels. Providers and channels have a one-to-many relationship. 39 | 40 | 1. While logged in to LINE Developers, access the LINE Developers Console. 41 | https://developers.line.biz/console/ 42 | 2. Click the Create button next to Provider. 43 | ![Creating a provider_1_ Image of the created part surrounded by a square](../images/en/line-provider-create-1-en.png) 44 | 3. Enter a provider name and click Create. 45 | 4. Confirm that the provider has been created and that your see this screen. 46 | ![Creating a Provider_2](../images/en/line-provider-create-2-en.png) 47 | 48 | ## 4. Create a channel 49 | 50 | 1. Create a channel for the Messaging API 51 | 1. On the provider screen that you just created, click Messaging API. 52 | ![Create Channel_Image surrounding the Messaging API part](../images/en/line-channel-create-1-en.png) 53 | 1. Set the items as follows: 54 | 1. Channel type: No change 55 | 1. Provider: No change 56 | 1. Channel icon: No change 57 | 1. Channel name: Any channel name 58 | *The channel name is the account name that will be displayed when the end user adds your account a friend. You can change it later. 59 | 1. Channel description: Any description 60 | 1. Large industry: Industry that matches the application content 61 | 1. Small industry: Industry that matches the application content 62 | 1. Email: No change 63 | 1. Privacy Policy URL: Optional 64 | 1. Terms of Service URL: Optional 65 | 1. Read the LINE Official Account Terms of Service and LINE Official Account API Terms of Service, and select "I agree." 66 | 1. Click Create to create a channel. 67 | 1. The screen of the created channel will appear as shown in the image below, confirming that the channel creation is complete.. 68 | ![Creating a channel_2](../images/en/line-channel-create-2-en.png) 69 | *Make a note of the channel ID and channel secret displayed on the basic channel settings tab, as they will be used in the following steps. 70 | 1. Create a channel for LIFF 71 | 1. On the provider screen that you just created, click LINE Login. 72 | ![Create a channel _ Image with a square around the LINE login part](../images/en/line-channel-create-3-en.png) 73 | 1. Set the items as follows: 74 | 1. Channel type: No change 75 | 1. Provider: No change 76 | 1. Region: Japan 77 | 1. Channel icon: No change 78 | 1. Channel name: Any channel name 79 | The channel name is the name displayed when you log in to LINE. You can change it later. 80 | 1. Channel description: Any description 81 | 1. App Type: Choose Web App 82 | 1. Email: No change 83 | 1. Privacy Policy URL: Optional 84 | 1. Terms of Service URL: Optional 85 | 1. Read and agree to the contents of the LINE Developers Agreement. 86 | 1. Click Create to create a channel. 87 | 1. The screen of the created channel will appear as shown in the image below, confirming that the channel creation is complete.. 88 | ![Create a channel_4](../images/en/line-channel-create-4-en.png) 89 | *Make a note of the channel ID displayed on the basic channel settings tab, as it will be used in the following steps. 90 | 1. "Linked bot" settings 91 | 1. In the basic channel settings tab of the LIFF channel you just created, click the Edit button of "Linked bots." 92 | 1. Select the Messaging API channel from the drop-down menu and update. 93 | 1. Add a LIFF app 94 | 1. In the LIFF channel created earlier, switch to the LIFF tab and click Add. 95 | ![Create channel_Add LIFF](../images/en/line-channel-create-add-liff-en.png) 96 | 1. Set the items as follows: 97 | 1. LIFF app name: Any app name 98 | 1. Size: Full 99 | *This is the setting for the vertical size of the application to be displayed in LINE. See the link below. You can change it later. 100 | https://developers.line.biz/en/docs/liff/overview/#screen-size 101 | 1. Endpoint URL: https://example.com 102 | *After creating the app, it will be set again, so enter a temporary value. 103 | 1. Scope: Select openid,profile 104 | 1. Bot-link function: On(Aggressive) 105 | This is a setting to encourage users to add the LINE Official Account as a friend when the LIFF app is launched. 106 | 1. Scan QR: off 107 | 1. Module mode: off 108 | 1. Click Add and confirm that the screen looks like the image below. 109 | ![Create a channel _ Add LIFF app](../images/en/line-channel-create-add-liff-app-en.png) 110 | *Make a note of the LIFF ID as it will be used in the following steps. 111 | 112 | 1. Create a channel for LINE Pay 113 | *In this step, you'll create a channel for demo operation using Sandbox. Actual LINE Pay billing won't be performed. If you want to use it as a production environment, implement it separately by the developer. 114 | 1. Go to [LINE Pay Developers](https://pay.line.me/tw/developers/techsupport/sandbox/testflow?locale=en_US). 115 | 1. Enter the following information for Sandbox generation and press Submit. 116 | 1. Country: JP 117 | 1. Service Type: Online 118 | 1. Currency: JPY 119 | 1. Email Address: Enter an available email address 120 | 1. Confirm that you have received an email from LINE Pay at the email address you entered. 121 | 1. Go to [pay.line.me](https://pay.line.me/portal/jp/auth/login) and log in to the merchant center with the account information (test ID and password) provided in the email. 122 | 1. After logging in, click the link Payment Linkage Management > Linkage Key Management in the left menu. 123 | ![Linkage key management](../images/en/linepay-key-en.png) 124 | 1. Enter the same password as when you logged in and then click Confirm. 125 | 1. Make a note of the Channel ID and Channel Secret Key displayed as shown in the image below, as they will be used later in the procedure. 126 | ![Channel information](../images/en/linepay-channel-information-en.png) 127 | 128 | [Next page](back-end-construction.md) 129 | 130 | [Back to Table of Contents](README_en.md) 131 | -------------------------------------------------------------------------------- /docs/en/test-data-charge.md: -------------------------------------------------------------------------------- 1 | # Input test data 2 | 3 | - Input test data to DynamoDB 4 | 5 | You need to input store information data for the operation of this app. 6 | Put the test data into the table with the table name set in the ItemListDBName of template.yaml when deploying the app. 7 | The test data is a json format string in the backend > APP folder: dynamodb_data/table_order_item_0.json ~ table_order_item_3.json, table_order_item_5.json. 8 | Paste and submit the file in the DynamoDB console of the AWS management console. (*See image below) 9 | 10 | [Input test data] 11 | ![Data input image](../images/en/test-data-charge-en.png) 12 | 13 | [Next page](validation.md) 14 | 15 | [Back to Table of Contents](README_en.md) 16 | -------------------------------------------------------------------------------- /docs/en/validation.md: -------------------------------------------------------------------------------- 1 | # Operation check 2 | 3 | ## LIFF endpoint configuration 4 | 5 | Set the endpoint URL of the LIFF app created in [Create LINE channel > Add LIFF app]. 6 | *To build a local environment, follow the steps in [Front-end development environment](front-end-development-environment.md) and enter the local URL. 7 | 8 | 1. In the [LINE Developers Console](https://developers.line.biz/console/), go to the LIFF app page created in [Create LINE channel > Add LIFF app]. 9 | ![LIFF console](../images/en/liff-console-en.png) 10 | 11 | 1. Click the Edit button of the Endpoint URL. 12 | ![Description of the endpoint URL](../images/en/end-point-url-description-en.png) 13 | 14 | 1. Enter the CloudFrontDomainName that you took a note of in the [Building the backend > Deploying the application (APP)] procedure with https:// at the beginning, and then click Update. 15 | ![Edit the endpoint URL](../images/en/end-point-url-editing-en.png) 16 | 17 | ## Rich menu settings 18 | 19 | If you want to set the rich menu and start the app, see this link to set it up. 20 | https://developers.line.biz/en/docs/messaging-api/using-rich-menus/#creating-a-rich-menu-with-the-line-manager 21 | 22 | # Operation check 23 | 24 | After completing all the steps, access the LIFF URL of the LIFF app created in the [Create LINE channel > Add LIFF app] procedure and check that it works. 25 | 26 | *It may take up to two hours to create the Cloudfront, so if you see the Access Denied screen, please wait a while before checking again. 27 | 28 | [Back to Table of Contents](README_en.md) 29 | -------------------------------------------------------------------------------- /docs/images/channel-access-token-table-record-en.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/line/line-api-use-case-table-order/40f420b8e6e4f1a90ef65c3fd26b9ef1c0555ef9/docs/images/channel-access-token-table-record-en.png -------------------------------------------------------------------------------- /docs/images/en/channel-access-token-table-record-en.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/line/line-api-use-case-table-order/40f420b8e6e4f1a90ef65c3fd26b9ef1c0555ef9/docs/images/en/channel-access-token-table-record-en.png -------------------------------------------------------------------------------- /docs/images/en/end-point-url-description-en.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/line/line-api-use-case-table-order/40f420b8e6e4f1a90ef65c3fd26b9ef1c0555ef9/docs/images/en/end-point-url-description-en.png -------------------------------------------------------------------------------- /docs/images/en/end-point-url-editing-en.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/line/line-api-use-case-table-order/40f420b8e6e4f1a90ef65c3fd26b9ef1c0555ef9/docs/images/en/end-point-url-editing-en.png -------------------------------------------------------------------------------- /docs/images/en/liff-console-en.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/line/line-api-use-case-table-order/40f420b8e6e4f1a90ef65c3fd26b9ef1c0555ef9/docs/images/en/liff-console-en.png -------------------------------------------------------------------------------- /docs/images/en/line-channel-create-1-en.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/line/line-api-use-case-table-order/40f420b8e6e4f1a90ef65c3fd26b9ef1c0555ef9/docs/images/en/line-channel-create-1-en.png -------------------------------------------------------------------------------- /docs/images/en/line-channel-create-2-en.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/line/line-api-use-case-table-order/40f420b8e6e4f1a90ef65c3fd26b9ef1c0555ef9/docs/images/en/line-channel-create-2-en.png -------------------------------------------------------------------------------- /docs/images/en/line-channel-create-3-en.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/line/line-api-use-case-table-order/40f420b8e6e4f1a90ef65c3fd26b9ef1c0555ef9/docs/images/en/line-channel-create-3-en.png -------------------------------------------------------------------------------- /docs/images/en/line-channel-create-4-en.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/line/line-api-use-case-table-order/40f420b8e6e4f1a90ef65c3fd26b9ef1c0555ef9/docs/images/en/line-channel-create-4-en.png -------------------------------------------------------------------------------- /docs/images/en/line-channel-create-add-liff-app-en.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/line/line-api-use-case-table-order/40f420b8e6e4f1a90ef65c3fd26b9ef1c0555ef9/docs/images/en/line-channel-create-add-liff-app-en.png -------------------------------------------------------------------------------- /docs/images/en/line-channel-create-add-liff-en.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/line/line-api-use-case-table-order/40f420b8e6e4f1a90ef65c3fd26b9ef1c0555ef9/docs/images/en/line-channel-create-add-liff-en.png -------------------------------------------------------------------------------- /docs/images/en/line-provider-create-1-en.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/line/line-api-use-case-table-order/40f420b8e6e4f1a90ef65c3fd26b9ef1c0555ef9/docs/images/en/line-provider-create-1-en.png -------------------------------------------------------------------------------- /docs/images/en/line-provider-create-2-en.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/line/line-api-use-case-table-order/40f420b8e6e4f1a90ef65c3fd26b9ef1c0555ef9/docs/images/en/line-provider-create-2-en.png -------------------------------------------------------------------------------- /docs/images/en/linepay-channel-information-en.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/line/line-api-use-case-table-order/40f420b8e6e4f1a90ef65c3fd26b9ef1c0555ef9/docs/images/en/linepay-channel-information-en.png -------------------------------------------------------------------------------- /docs/images/en/linepay-key-en.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/line/line-api-use-case-table-order/40f420b8e6e4f1a90ef65c3fd26b9ef1c0555ef9/docs/images/en/linepay-key-en.png -------------------------------------------------------------------------------- /docs/images/en/out-put-description-en.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/line/line-api-use-case-table-order/40f420b8e6e4f1a90ef65c3fd26b9ef1c0555ef9/docs/images/en/out-put-description-en.png -------------------------------------------------------------------------------- /docs/images/en/test-data-charge-en.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/line/line-api-use-case-table-order/40f420b8e6e4f1a90ef65c3fd26b9ef1c0555ef9/docs/images/en/test-data-charge-en.png -------------------------------------------------------------------------------- /docs/images/en/test-event-set-en.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/line/line-api-use-case-table-order/40f420b8e6e4f1a90ef65c3fd26b9ef1c0555ef9/docs/images/en/test-event-set-en.png -------------------------------------------------------------------------------- /docs/images/end-point-url-description-en.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/line/line-api-use-case-table-order/40f420b8e6e4f1a90ef65c3fd26b9ef1c0555ef9/docs/images/end-point-url-description-en.png -------------------------------------------------------------------------------- /docs/images/end-point-url-editing-en.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/line/line-api-use-case-table-order/40f420b8e6e4f1a90ef65c3fd26b9ef1c0555ef9/docs/images/end-point-url-editing-en.png -------------------------------------------------------------------------------- /docs/images/jp/channel-access-token-table-record.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/line/line-api-use-case-table-order/40f420b8e6e4f1a90ef65c3fd26b9ef1c0555ef9/docs/images/jp/channel-access-token-table-record.png -------------------------------------------------------------------------------- /docs/images/jp/end-point-url-description.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/line/line-api-use-case-table-order/40f420b8e6e4f1a90ef65c3fd26b9ef1c0555ef9/docs/images/jp/end-point-url-description.png -------------------------------------------------------------------------------- /docs/images/jp/end-point-url-editing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/line/line-api-use-case-table-order/40f420b8e6e4f1a90ef65c3fd26b9ef1c0555ef9/docs/images/jp/end-point-url-editing.png -------------------------------------------------------------------------------- /docs/images/jp/liff-console.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/line/line-api-use-case-table-order/40f420b8e6e4f1a90ef65c3fd26b9ef1c0555ef9/docs/images/jp/liff-console.png -------------------------------------------------------------------------------- /docs/images/jp/line-channel-create-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/line/line-api-use-case-table-order/40f420b8e6e4f1a90ef65c3fd26b9ef1c0555ef9/docs/images/jp/line-channel-create-1.png -------------------------------------------------------------------------------- /docs/images/jp/line-channel-create-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/line/line-api-use-case-table-order/40f420b8e6e4f1a90ef65c3fd26b9ef1c0555ef9/docs/images/jp/line-channel-create-2.png -------------------------------------------------------------------------------- /docs/images/jp/line-channel-create-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/line/line-api-use-case-table-order/40f420b8e6e4f1a90ef65c3fd26b9ef1c0555ef9/docs/images/jp/line-channel-create-3.png -------------------------------------------------------------------------------- /docs/images/jp/line-channel-create-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/line/line-api-use-case-table-order/40f420b8e6e4f1a90ef65c3fd26b9ef1c0555ef9/docs/images/jp/line-channel-create-4.png -------------------------------------------------------------------------------- /docs/images/jp/line-channel-create-add-liff-app.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/line/line-api-use-case-table-order/40f420b8e6e4f1a90ef65c3fd26b9ef1c0555ef9/docs/images/jp/line-channel-create-add-liff-app.png -------------------------------------------------------------------------------- /docs/images/jp/line-channel-create-add-liff.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/line/line-api-use-case-table-order/40f420b8e6e4f1a90ef65c3fd26b9ef1c0555ef9/docs/images/jp/line-channel-create-add-liff.png -------------------------------------------------------------------------------- /docs/images/jp/line-provider-create-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/line/line-api-use-case-table-order/40f420b8e6e4f1a90ef65c3fd26b9ef1c0555ef9/docs/images/jp/line-provider-create-1.png -------------------------------------------------------------------------------- /docs/images/jp/line-provider-create-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/line/line-api-use-case-table-order/40f420b8e6e4f1a90ef65c3fd26b9ef1c0555ef9/docs/images/jp/line-provider-create-2.png -------------------------------------------------------------------------------- /docs/images/jp/linepay-channel-information.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/line/line-api-use-case-table-order/40f420b8e6e4f1a90ef65c3fd26b9ef1c0555ef9/docs/images/jp/linepay-channel-information.png -------------------------------------------------------------------------------- /docs/images/jp/linepay-key.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/line/line-api-use-case-table-order/40f420b8e6e4f1a90ef65c3fd26b9ef1c0555ef9/docs/images/jp/linepay-key.png -------------------------------------------------------------------------------- /docs/images/jp/out-put-description.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/line/line-api-use-case-table-order/40f420b8e6e4f1a90ef65c3fd26b9ef1c0555ef9/docs/images/jp/out-put-description.png -------------------------------------------------------------------------------- /docs/images/jp/test-data-charge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/line/line-api-use-case-table-order/40f420b8e6e4f1a90ef65c3fd26b9ef1c0555ef9/docs/images/jp/test-data-charge.png -------------------------------------------------------------------------------- /docs/images/jp/test-event-set.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/line/line-api-use-case-table-order/40f420b8e6e4f1a90ef65c3fd26b9ef1c0555ef9/docs/images/jp/test-event-set.png -------------------------------------------------------------------------------- /docs/images/liff-console-en.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/line/line-api-use-case-table-order/40f420b8e6e4f1a90ef65c3fd26b9ef1c0555ef9/docs/images/liff-console-en.png -------------------------------------------------------------------------------- /docs/images/line-channel-create-1-en.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/line/line-api-use-case-table-order/40f420b8e6e4f1a90ef65c3fd26b9ef1c0555ef9/docs/images/line-channel-create-1-en.png -------------------------------------------------------------------------------- /docs/images/line-channel-create-2-en.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/line/line-api-use-case-table-order/40f420b8e6e4f1a90ef65c3fd26b9ef1c0555ef9/docs/images/line-channel-create-2-en.png -------------------------------------------------------------------------------- /docs/images/line-channel-create-3-en.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/line/line-api-use-case-table-order/40f420b8e6e4f1a90ef65c3fd26b9ef1c0555ef9/docs/images/line-channel-create-3-en.png -------------------------------------------------------------------------------- /docs/images/line-channel-create-4-en.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/line/line-api-use-case-table-order/40f420b8e6e4f1a90ef65c3fd26b9ef1c0555ef9/docs/images/line-channel-create-4-en.png -------------------------------------------------------------------------------- /docs/images/line-channel-create-add-liff-app-en.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/line/line-api-use-case-table-order/40f420b8e6e4f1a90ef65c3fd26b9ef1c0555ef9/docs/images/line-channel-create-add-liff-app-en.png -------------------------------------------------------------------------------- /docs/images/line-channel-create-add-liff-en.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/line/line-api-use-case-table-order/40f420b8e6e4f1a90ef65c3fd26b9ef1c0555ef9/docs/images/line-channel-create-add-liff-en.png -------------------------------------------------------------------------------- /docs/images/line-provider-create-1-en.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/line/line-api-use-case-table-order/40f420b8e6e4f1a90ef65c3fd26b9ef1c0555ef9/docs/images/line-provider-create-1-en.png -------------------------------------------------------------------------------- /docs/images/line-provider-create-2-en.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/line/line-api-use-case-table-order/40f420b8e6e4f1a90ef65c3fd26b9ef1c0555ef9/docs/images/line-provider-create-2-en.png -------------------------------------------------------------------------------- /docs/images/linepay-channel-information-en.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/line/line-api-use-case-table-order/40f420b8e6e4f1a90ef65c3fd26b9ef1c0555ef9/docs/images/linepay-channel-information-en.png -------------------------------------------------------------------------------- /docs/images/linepay-key-en.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/line/line-api-use-case-table-order/40f420b8e6e4f1a90ef65c3fd26b9ef1c0555ef9/docs/images/linepay-key-en.png -------------------------------------------------------------------------------- /docs/images/out-put-description-en.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/line/line-api-use-case-table-order/40f420b8e6e4f1a90ef65c3fd26b9ef1c0555ef9/docs/images/out-put-description-en.png -------------------------------------------------------------------------------- /docs/images/test-data-charge-en.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/line/line-api-use-case-table-order/40f420b8e6e4f1a90ef65c3fd26b9ef1c0555ef9/docs/images/test-data-charge-en.png -------------------------------------------------------------------------------- /docs/images/test-event-set-en.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/line/line-api-use-case-table-order/40f420b8e6e4f1a90ef65c3fd26b9ef1c0555ef9/docs/images/test-event-set-en.png -------------------------------------------------------------------------------- /docs/jp/back-end-construction.md: -------------------------------------------------------------------------------- 1 | # バックエンドの構築手順 2 | 3 | ## 周辺リソースのデプロイ 4 | 5 | 本アプリでは以下の周辺リソースをデプロイする必要があります。 6 | 7 | 1. 共通処理レイヤー(Layer) 8 | 1. 定期実行バッチ(batch) 9 | 10 | ### 1.共通処理レイヤー(Layer) 11 | 12 | AWS Lambda では複数 Lambda 関数で共通化して利用したい処理をレイヤーとして記述することが出来ます。 13 | 本アプリではレイヤーを利用しているので、はじめに以下の手順で、レイヤーをデプロイしてください。 14 | 15 | - template.yaml の修正 16 | backend-> Layer フォルダ内の template.yaml を開き、EnvironmentMap の dev の以下のパラメータ項目を修正する。 17 | 18 | - `LayerName` 任意のレイヤー名 19 | 20 | - 以下コマンドの実行 21 | 22 | ``` 23 | cd [backend -> Layerのフォルダ] 24 | sam build --use-container 25 | sam deploy --guided 26 | ※プロファイル情報(default)以外を使用する場合は指定必要 sam deploy --guided --profile xxx 27 | Stack Name : 任意のスタック名 28 | AWS Region : ap-northeast-1 29 | Parameter Environment: dev 30 | #Shows you resources changes to be deployed and require a 'Y' to initiate deploy Confirm changes before deploy [Y/n]: Y 31 | #SAM needs permission to be able to create roles to connect to the resources in your template Allow SAM CLI IAM role creation[Y/n]: Y 32 | Save arguments to samconfig.toml [Y/n]: Y 33 | 34 | SAM configuration file [samconfig.toml]: 入力せずEnter 35 | SAM configuration environment [default]: 入力せずEnter 36 | 37 | Deploy this changeset? [y/N]: y 38 | ``` 39 | 40 | - レイヤーバージョンをメモ 41 | デプロイ後、ターミナルの Outputs の項目に、レイヤー ARN とレイヤーバージョンが表示されるので、レイヤーバージョンをメモしておく。 42 | レイヤーバージョンは末尾の数字。 43 | ※バージョンはデプロイするたびに更新されるので、初めてのデプロイの場合バージョン 1 となっているのが正しいです。 44 | ![コマンドプロンプトのOutput部の画像](../images/jp/out-put-description.png) 45 | 46 | - 【確認】AWS マネジメントコンソールで Lambda のコンソールを開き、左タブから「レイヤー」を選択し、今回デプロイしたレイヤーがあることを確認する。 47 | 48 | ### 2.定期実行バッチ(batch) 49 | 本アプリで必要な短期チャネルアクセストークン更新バッチをデプロイします。 50 | 短期チャネルアクセストークンは有効期限が取得後 30 日間なので、有効期限の前に短期チャネルアクセストークンを再取得してテーブル更新をするバッチを毎日定刻に動作させています。 51 | 定刻にバッチを動作させるために、Amazon EventBridge([公式ドキュメント](https://docs.aws.amazon.com/ja_jp/eventbridge/latest/userguide/what-is-amazon-eventbridge.html))を用いています。 52 | 以下の手順に従い、バッチのデプロイを行ってください。 53 | 54 | - template.yaml の修正 55 | backend-> batch フォルダ内の template.yaml を開き、EnvironmentMap の dev の以下のパラメータ項目を修正する。 56 | 57 | - `LINEChannelAccessTokenDBName` 任意のテーブル名(短期チャネルアクセストークンを管理するテーブル) 58 | - `EventBridgeName` 任意のイベントブリッジ名 59 | 例) AccessTokenUpdateEvent 60 | - `LayerVersion` 【1.共通処理レイヤー】の手順にてデプロイしたレイヤーのバージョン番号 61 | 例)LayerVersion: 1 62 | - `LoggerLevel` INFO or Debug 63 | 例)INFO 64 | 65 | - 以下コマンドの実行 66 | 67 | ``` 68 | cd [backend -> batch]のtemplate.yamlが配置されたフォルダ] 69 | sam build --use-container 70 | sam deploy --guided 71 | ※プロファイル情報(default)以外を使用する場合は指定必要 sam deploy --guided --profile xxx 72 | Stack Name : 任意のスタック名 73 | AWS Region : ap-northeast-1 74 | Parameter Environment: dev 75 | #Shows you resources changes to be deployed and require a 'Y' to initiate deploy Confirm changes before deploy [Y/n]: Y 76 | #SAM needs permission to be able to create roles to connect to the resources in your template Allow SAM CLI IAM role creation[Y/n]: Y 77 | Save arguments to samconfig.toml [Y/n]: Y 78 | 79 | SAM configuration file [samconfig.toml]: 入力せずEnter 80 | SAM configuration environment [default]: 入力せずEnter 81 | 82 | Deploy this changeset? [y/N]: y 83 | ``` 84 | 85 | - テーブルにチャネル ID とチャネルシークレットを登録する 86 | - AWS マネジメントコンソールにログインし、DynamoDB のコンソールを開く 87 | - 先ほど作成した「短期チャネルアクセストークンを管理するテーブル」にて項目の作成を行い、【LINE チャネルの作成】で作成したMessaging APIのチャネルのチャネル ID とチャネルシークレットを以下の通り登録する。 88 | なお、チャネル ID とチャネルシークレットは[LINE Developers コンソール](https://developers.line.biz/console/)のチャネル基本設定にて確認可能。 89 | - channelId: チャネル ID (文字列) 90 | - channelSecret : チャネルシークレット(文字列) 91 | ![チャネルアクセストークンの登録](../images/jp/channel-access-token-table-record.png) 92 | - チャネルアクセストークン更新の Lambda 関数を実行する 93 | - AWS マネジメントコンソールにログインし、Lambda のコンソールを開く 94 | - 先ほど作成した Lambda 関数(関数名は TableOrder-PutAccessToken-{Enviromentで指定した値})を開く 95 | - Lambda 関数のコンソール右上、テストイベントの選択プルダウンにて「テストイベントの設定」を選択する 96 | - 以下のようなウィンドウが開いたら、イベント名を入力し、イベント内容を空にして作成ボタンを押下する。 97 | ![テストイベントの設定](../images/jp/test-event-set.png) 98 | - Lambda 関数のコンソール右上、テストボタンを押下してテスト実行を行う 99 | - 【確認】AWS マネジメントコンソールの DynamoDB コンソールにて、チャネルアクセストークンのテーブル開き、本アプリで利用する LINE チャネル ID のデータに channelAccessToken,limitDate,updatedTime の項目が追加されていることを確認する。 100 | 101 | ## アプリのデプロイ(APP) 102 | 103 | 以下の手順で、アプリ本体をデプロイしてください。 104 | 105 | - template.yaml の修正 106 | backend -> APP フォルダ内の template.yaml を開き、EnvironmentMap の dev の以下のパラメータ項目を修正する。 107 | ※S3のアクセスログが必要な場合、ACCESS LOG SETTING とコメントされている箇所のコメントを解除してください。 108 | 109 | - `LineChannelId` 【LINE チャネルの作成】で作成したMessaging APIチャネルのチャネル ID 110 | - `LIFFChannelId` 【LINE チャネルの作成】で作成したLIFFチャネルのチャネル ID 111 | - `LiffUrl` 【LINE チャネルの作成】で作成したLIFFアプリの LIFF URL 112 | - `LinePayChannelId` 【LINE チャネルの作成】で作成したLINE Payチャネルのチャネル ID 113 | - `LinePayChannelSecret` 【LINE チャネルの作成】で作成したLINE Payチャネルのチャネルシークレット 114 | - `LinePayIsSandbox` True or False 115 | ※基本Trueを指定してください。 116 | True: Sandbox環境を使用します。LINEPayによる課金は行われません。 117 | False: LINEPay「テスト加盟店環境」を使用します。一時的に利用者のLINEPay残高が実際に引き落とされた状態となり、一定時間後に利用額が返金されます。 118 | - `ItemListDBName` 任意のテーブル名(商品情報を登録するテーブル) 119 | - `PaymentInfoDBName` 任意のテーブル名(支払い情報を登録するテーブル) 120 | - `LINEChannelAccessTokenDBName` 【2.定期実行バッチ】の手順でデプロイした「短期チャネルアクセストークンを管理するテーブル」のテーブル名 121 | - `FrontS3BucketName` 任意のバケット名 ※フロント側モジュールを配置するための S3 バケット名になります。 122 | - `LayerVersion` 【1.共通処理レイヤー】の手順にてデプロイしたレイヤーのバージョン番号 123 | 例)LayerVersion: 1 124 | - `LoggerLevel` INFO or Debug 125 | - `LambdaMemorySize` Lambdaのメモリサイズ 126 | 例)LambdaMemorySize: 128 ※特に変更する必要がない場合、最小サイズの128を指定してください。 127 | - `TTL` True or False (注文情報を自動で削除するか否か) 128 | - `TTLDay` 任意の数値 (TTLがTrueのとき、予約情報を登録から何日後に削除するか指定。TTLがFalseのとき、0を入れてください。) 129 | - `LogS3Bucket` 任意のバケット名(アクセスログを保管するS3の名称) 130 | ※アクセスログが必要な場合のみコメントを解除して記載してください。また、他UseCaseアプリを構築済みの方は、他UseCaseアプリのアクセスログバケット名と別名で指定してください。 131 | - `LogFilePrefix` 任意の名称(ログファイルの接頭辞) 132 | ※アクセスログが必要な場合のみコメントを解除して記載してください。 133 | 134 | - 以下コマンドの実行 135 | 136 | ``` 137 | cd [backend -> APP のフォルダ] 138 | sam build --use-container 139 | sam deploy --guided 140 | ※プロファイル情報(default)以外を使用する場合は指定必要 sam deploy --guided --profile xxx 141 | Stack Name : 任意のスタック名 142 | AWS Region : ap-northeast-1 143 | Parameter Environment: dev 144 | #Shows you resources changes to be deployed and require a 'Y' to initiate deploy Confirm changes before deploy [Y/n]: Y 145 | #SAM needs permission to be able to create roles to connect to the resources in your template Allow SAM CLI IAM role creation[Y/n]: Y 146 | ××××× may not have authorization defined, Is this okay? [y/N]: y (全てyと入力) 147 | Save arguments to samconfig.toml [Y/n]: Y 148 | 149 | SAM configuration file [samconfig.toml]: 入力せずEnter 150 | SAM configuration environment [default]: 入力せずEnter 151 | 152 | Deploy this changeset? [y/N]: y 153 | ``` 154 | 155 | - API Gateway endpoint URLとCloudFrontDomainNameのメモ 156 | デプロイ成功時にOutPutにて表示されるAPI Gateway endpoint URLとCloudFrontDomainNameのメモを取ってください。 157 | ## エラー対応 158 | - デプロイ時、以下のようなエラーが出た場合、こちらの手順で解消してください。 159 | ``` 160 | Export with name xxxxx is already exported by stack sam-app. Rollback requested by user. 161 | ``` 162 | - backend -> Layer -> template.yamlを以下を参考に、修正後デプロイ 163 | ``` 164 | Outputs: 165 | UseCaseLayerName: 166 | Description: "UseCaseLayerDev Layer Name" 167 | Value: !FindInMap [EnvironmentMap, !Ref Environment, LayerName] 168 | Export: 169 | Name: TableOrderLayerDev -> こちらを任意の名称に修正 170 | ``` 171 | - backend -> batch -> template.yamlを、以下の記載を参考に修正する。複数あるので、すべて修正する。 172 | ``` 173 | !ImportValue TableOrderLayerDev -> TableOrderLayerDev を先ほど入力した名称に修正 174 | ``` 175 | - backend -> APP -> template.yamlを、以下の記載を参考に修正する。複数あるので、すべて修正する。 176 | ``` 177 | !ImportValue TableOrderLayerDev -> TableOrderLayerDev を先ほど入力した名称に修正 178 | ``` 179 | 180 | ※本番環境構築中の方はこちらのリンクで次の頁へ移動してください 181 | [次の頁へ(本番環境)](front-end-construction.md) 182 | 183 | ※ローカル環境構築中の方はこちらのリンクで次の頁へ移動してください 184 | [次の頁へ(ローカル環境)](front-end-development-environment.md) 185 | 186 | [目次へ戻る](../../README.md) 187 | -------------------------------------------------------------------------------- /docs/jp/front-end-construction.md: -------------------------------------------------------------------------------- 1 | ## 本番(AWS)フロントエンド環境構築 2 | 1. .env ファイルの設定 3 | 4 | front/.env ファイルの `LIFF_ID` に LIFF アプリの LIFF ID を、`BASE_URL` に AWS APIGateway の URL を、`APIGATEWAY_STAGE`に AWS APIGateway のステージ名を設定してください。 5 | 6 | ▼ .env ファイル 7 | ```` 8 | # LIFF ID 9 | LIFF_ID=9999999999-xxxxxxxx 10 | 11 | # AXIOS BASE URL 12 | BASE_URL=https://xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com 13 | 14 | # API Gateway Stage 15 | APIGATEWAY_STAGE=dev 16 | 17 | # Ajax Module (axios or amplify) 18 | AJAX_MODULE=amplify 19 | ```` 20 | 21 | 1. node_modules インストール 22 | 23 | front プロジェクトに Node.js の依存パッケージ(※ node_modules フォルダ)がインストールされていない場合、front フォルダー直下で以下のコマンドを実行して node_modules をインストールしてください。 24 | ``` 25 | npm install 26 | ``` 27 | もしくは 28 | ``` 29 | yarn install 30 | ``` 31 | 32 | 1. 静的ビルド 33 | 34 | フロント側をビルドして S3 に配置する静的モジュール生成します。 front フォルダ直下で以下のコマンドを実行してください。 35 | ``` 36 | npm run build 37 | ``` 38 | もしくは 39 | ``` 40 | yarn run build 41 | ``` 42 | ビルドが完了したら front/dist フォルダが生成されています。 dist フォルダ内のファイルが S3 配置対象になります。 43 | 44 | 1. S3 にフロントエンドのモジュールを配置 45 | 46 | 上記、静的ビルドでビルドしたモジュール (※ dist フォルダの中身全部) を対象のS3に配置してください。 47 | 48 | 49 | [次の頁へ](test-data-charge.md) 50 | 51 | [目次へ戻る](../../README.md) 52 | -------------------------------------------------------------------------------- /docs/jp/front-end-development-environment.md: -------------------------------------------------------------------------------- 1 | ## ローカルフロントエンド環境構築 2 | フロントエンドの開発は Nuxt.js プロジェクトの SPA (Single Page Application) で開発を行います。ローカル環境で Nuxt 開発用サーバーの起動や本番モジュールの静的ビルドを行います。ソースコードダウンロード後、ローカル環境で以下の作業を行ってください。 3 | 4 | - .env ファイルの設定 5 | 6 | .env ファイルにフロントのアプリケーションで使用する値を設定してください。 7 | 8 | ▼ .env ファイル 9 | ``` 10 | # LIFF ID 11 | LIFF_ID=9999999999-xxxxxxxx 12 | 13 | # AXIOS BASE URL 14 | BASE_URL=https://xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com 15 | 16 | # API Gateway Stage 17 | APIGATEWAY_STAGE=dev 18 | 19 | # Ajax Module (axios or amplify) 20 | AJAX_MODULE=amplify 21 | ``` 22 | 23 | - `LIFF_ID` には LINE チャンネルの LIFF アプリの LIFF ID を設定 24 | - `BASE_URL` には AWS APIGateway の URL を設定 25 | - `APIGATEWAY_STAGE` には AWS APIGateway のステージ名を設定 26 | - `AJAX_MODULE` には Ajax 通信の時に使用するモジュール( Amplify API は "amplify" / Axios は "axios" )を設定 27 | 28 | - node_modules インストール 29 | 30 | front プロジェクトに Node.js の依存パッケージ(※ node_modules フォルダ)がインストールされていない場合、 front フォルダー直下で以下のコマンドを実行して node_modules をインストールしてください。 31 | ``` 32 | npm install 33 | ``` 34 | もしくは 35 | ``` 36 | yarn install 37 | ``` 38 | インストールが完了したら front/`node_modules` フォルダが生成されています。 39 | 40 | - LIFF アプリのエンドポイントURLの修正 41 | 42 | ローカル開発環境の Web サーバーで開発する為、 LINE チャネルの LIFF アプリの`エンドポイントURL`を以下の URL に変更してください(※開発完了後 CloudFront の URL に戻してください)。 43 | ``` 44 | https://localhost:3000 45 | ``` 46 | 47 | - Nuxt 開発サーバーの HTTPS 通信化 48 | 49 | LINE チャネルの LIFF アプリの`エンドポイントURL`は https から始まる URL しか設定できません。その為、SSL 証明書使用して Nuxt 開発サーバーを HTTPS 通信化してください。以下の場所にSSL証明書(`localhost.key`, `localhost.crt`)を配置します。 50 | ``` 51 | front/cert 52 | ``` 53 | 配置後、`nuxt.config.js`の下記のコメントアウト`//`を取ります。 54 | ``` 55 | export default { 56 | telemetry: false, 57 | mode: 'spa', 58 | generate: { 59 | dir: './dist' 60 | }, 61 | server: { 62 | port: 3000, 63 | host: '0.0.0.0', 64 | timing: false, 65 | https: { 66 | // key: fs.readFileSync('./cert/localhost.key'), 67 | // cert: fs.readFileSync('./cert/localhost.crt') 68 | } 69 | }, 70 | ``` 71 | 【参考】 72 | SSL 証明書(自己署名証明書)作成の1例 73 | ※opensslコマンド使って作成する方法 74 | ``` 75 | # 秘密鍵ファイル生成 76 | openssl genrsa -out localhost.key 2048 77 | # 証明書署名要求ファイル生成 78 | openssl req -new -key localhost.key -out localhost.csr -subj '/C=JP/ST=Tokyo/L=Tokyo/O=Example Ltd./OU=Web/CN=localhost' 79 | # 証明書生成 80 | openssl x509 -in localhost.csr -days 3650 -req -signkey localhost.key -out localhost.crt 81 | ``` 82 | 83 | - Nuxt 開発サーバーの起動 84 | 85 | ローカル環境での開発は Nuxt 開発サーバーを起動して行います。 front フォルダ直下で以下のコマンドを実行して Nuxt 開発サーバーを起動してください。 `https://localhost:3000` にアクセスできるようになります。上記 「 LIFF アプリのエンドポイントURLの修正 」を設定済みの場合、 LIFF アプリの LIFF URL (例:`https://liff.line.me/9999999999-xxxxxxxx`) でローカル環境にアクセスして開発を行えます。 86 | ``` 87 | npm run dev 88 | ``` 89 | もしくは 90 | ``` 91 | yarn run dev 92 | ``` 93 | 94 | 95 | 96 | [次の頁へ](test-data-charge.md) 97 | 98 | [目次へ戻る](../../README.md) 99 | -------------------------------------------------------------------------------- /docs/jp/liff-channel-create.md: -------------------------------------------------------------------------------- 1 | # LINE チャネルの作成 2 | 3 | チャネルは開発者が作成したシステムと LINE プラットフォームの通信を行うための通信路です。 4 | 本アプリでは以下の LINE チャネルが必要となるので、手順に従って作成してください。 5 | 6 | 1. MessagingAPI 用 LINE チャネル 7 | ・チャネル作成:https://developers.line.biz/ja/docs/clova-extensions-kit/create-messaging-api-channel-t4/ 8 | ・Messaging APIについて:https://lineapiusecase.com/ja/api/msgapi.html 9 | 10 | 1. LIFF 用 LINE チャネル 11 | ・チャネル作成:https://developers.line.biz/ja/docs/liff/registering-liff-apps/ 12 | ・LIFFについて:https://lineapiusecase.com/ja/api/miniliff.html 13 | 14 | 1. LINEPay 用 チャネル 15 | ・チャネル作成:https://pay.line.me/tw/developers/techsupport/sandbox/testflow?locale=ja_JP 16 | ・LINEPayについて:https://lineapiusecase.com/ja/api/pay.html 17 | 18 | ## 1.LINE アカウントの作成 19 | 20 | ※LINE アカウントを所持していない場合のみ、こちらの手順を行ってください。 21 | 22 | 1. 以下リンクのダウンロードより、LINE アプリをダウンロードし、LINE アカウントを作成する 23 | https://line.me/ja/ 24 | 25 | ## 2.LINE Developers へのログイン 26 | 27 | LINE Developers は本アプリで必要な LINE 公式アカウントの作成や、LIFF アプリの作成を行うための開発者向けサイトです。LINE アカウントを用いて LINE Developers サイトにログインすることで、アプリの開発が可能になります。 28 | 29 | 1. 以下リンクにアクセスし、ログイン画面に遷移する 30 | https://developers.line.biz/ja/ 31 | 1. 「LINE アカウントでログイン」を行う 32 | 1. LINE アカウントに設定したメールアドレスとパスワードの入力を行うか、QR コードログインにより QR コードを別デバイスより読み取ってログインを行う 33 | ※メールアドレスによるログインを利用したい場合は別途 LINE アプリ側で設定する必要があります。詳細は以下のリンク参照。 34 | https://appllio.com/line-mail-address-settings 35 | 36 | ## 3.プロバイダーの作成 37 | 38 | プロバイダーは複数のチャネルを管理するチーム・会社・個人のようなひとまとまりの単位となります。プロバイダーとチャネルは一対多の関係です。 39 | 40 | 1. LINE Developes にログインした状態で、以下のコンソールに遷移する 41 | https://developers.line.biz/console/ 42 | 2. プロバイダー見出しの横にある作成ボタンを押下する 43 | ![プロバイターの作成_1_作成部分を四角で囲んだ画像](../images/jp/line-provider-create-1.png) 44 | 3. 任意のプロバイダー名を入力し、作成を押下する 45 | 4. プロバイダーが作成され、以下のような画面に遷移したことを確認する 46 | ![プロバイターの作成_2](../images/jp/line-provider-create-2.png) 47 | 48 | ## 4.チャネルの作成 49 | 50 | 1. Messaging API 用のチャネルを作成 51 | 1. 先ほど作成したプロバイダーの画面にて、Messaging API を押下する 52 | ![チャネルの作成_Messaging APIの部分を資格で囲んだ画像](../images/jp/line-channel-create-1.png) 53 | 1. 以下の通り、項目を設定する 54 | 1. チャネルの種類: 変更無し 55 | 1. プロバイダー: 変更無し 56 | 1. チャネルアイコン: 変更無し 57 | 1. チャネル名: 任意のチャネル名 58 | ※チャネル名はエンドユーザーが友達追加する際に表示されるアカウント名となります。後から変更可能です。 59 | 1. チャネル説明: 任意の説明 60 | 1. 大業種: アプリ内容に即した業種 61 | 1. 小業種: アプリ内容に即した業種 62 | 1. メールアドレス: 変更無し 63 | 1. プライバシーポリシー URL: 任意 64 | 1. サービス利用規約 URL: 任意 65 | 1. LINE 公式アカウント利用規約と LINE 公式アカウント API 利用規約に目を通して同意にチェックを入れる 66 | 1. 作成を押下し、チャネルを作成する 67 | 1. 以下画像のような作成したチャネルの画面が表示され、チャネルの作成が完了したことを確認する。 68 | ![チャネルの作成_2](../images/jp/line-channel-create-2.png) 69 | ※チャネル基本設定のタブに表示されているチャネル ID とチャネルシークレットを以降の手順にて使用するので、メモを取っておいてください。 70 | 1. LIFF 用のチャネルを作成 71 | 1. 先ほど作成したプロバイダーの画面にて、LINE ログインを押下する 72 | ![チャネルの作成_LINEログインの部分を四角で囲んだ画像](../images/jp/line-channel-create-3.png) 73 | 1. 以下の通り、項目を設定する 74 | 1. チャネルの種類: 変更無し 75 | 1. プロバイダー: 変更無し 76 | 1. 地域: 日本 77 | 1. チャネルアイコン: 変更無し 78 | 1. チャネル名: 任意のチャネル名 79 | ※チャネル名は LINE ログイン時に表示される名称となります。後から変更可能です。 80 | 1. チャネル説明: 任意の説明 81 | 1. アプリタイプ: ウェブアプリにチェックを入れる 82 | 1. メールアドレス: 変更無し 83 | 1. プライバシーポリシー URL: 任意 84 | 1. サービス利用規約 URL: 任意 85 | 1. LINE Developers Agreement の内容に目を通して同意にチェックを入れる 86 | 1. 作成を押下し、チャネルを作成する 87 | 1. 以下画像のような作成したチャネルの画面が表示され、チャネルの作成が完了したことを確認する。 88 | ![チャネルの作成_4](../images/jp/line-channel-create-4.png) 89 | ※チャネル基本設定のタブに表示されているチャネル IDを以降の手順にて使用するので、メモを取っておいてください。 90 | 1. 「リンクされたボット」の設定 91 | 1. 先ほど作成したLIFFのチャネルのチャネル基本設定のタブにて、「リンクされたボット」項目の編集ボタンを押下する。 92 | 1. プルダウンからMessagingAPIのチャネルを選択し、更新を行う。 93 | 1. LIFF アプリの追加 94 | 1. 先ほど作成した LIFF のチャネルにて、LIFF のタブに切り替え、追加ボタンを押下する 95 | ![チャネルの作成_LIFFの追加](../images/jp/line-channel-create-add-liff.png) 96 | 1. 以下の通り、項目を設定する 97 | 1. LIFF アプリ名: 任意のアプリ名 98 | 1. サイズ: Full 99 | ※ LINE 内で表示するアプリの縦サイズの設定です。以下リンク参照。後から変更可能です。 100 | https://developers.line.biz/ja/docs/liff/overview/#screen-size 101 | 1. エンドポイント URL: https://example.com 102 | ※アプリの作成後、再度設定するので仮の値を入れておきます。 103 | 1. Scope: openid, profile にチェック 104 | 1. ボットリンク機能: On(Aggressive) 105 | ※LIFF アプリ起動時に、紐づけられた LINE 公式アカウントの友達追加をどのように促すかという設定です。 106 | 1. Scan QR: off 107 | 1. モジュールモード: off 108 | 1. 追加を押下し、以下画像のような画面に遷移したことを確認する。 109 | ![チャネルの作成_LIFFアプリの追加](../images/jp/line-channel-create-add-liff-app.png) 110 | ※LIFF ID を以降の手順にて使用するので、メモを取っておいてください。 111 | 112 | 1. LINE Pay用のチャネルを作成 113 | ※こちらの手順ではSandboxを利用したデモ動作用のチャネルを作成します。実際のLINEPayによる課金は行われません。本番環境として利用する場合は、開発者が別途実装をお願い致します。 114 | 1. [LINE Pay Developers](https://pay.line.me/tw/developers/techsupport/sandbox/creation?locale=ja_JP)にアクセスする。 115 | 1. Sandbox生成に以下の必要事項を入力し、Submitを押下。 116 | 1. 国家: JP 117 | 1. サービスタイプ: Online 118 | 1. 通貨: JPY 119 | 1. Emailアドレス: 利用できるアドレスを入力 120 | 1. 入力したメールアドレス宛てにLINE Payからメールが来ていることを確認する。 121 | 1. [pay.line.me](https://pay.line.me/portal/jp/auth/login)にアクセスし、メールに記載されたアカウント情報(テストIDとパスワード)にて加盟店センターにログインする。 122 | 1. ログイン後、左メニューの決済連動管理 -> 連動キー管理 のリンクを押下 123 | ![連動キー管理](../images/jp/linepay-key.png) 124 | 1. ログイン時と同様のパスワードを入力し、確認を押下 125 | 1. 以下画像のように表示されたChannel IDとChannel Secret Keyを後ほどの手順で利用するのでメモしておく。 126 | ![チャネル情報](../images/jp/linepay-channel-information.png) 127 | 128 | [次の頁へ](back-end-construction.md) 129 | 130 | [目次へ戻る](../../README.md) 131 | -------------------------------------------------------------------------------- /docs/jp/test-data-charge.md: -------------------------------------------------------------------------------- 1 | # テストデータの投入 2 | - DynamoDB へテストデータの投入 3 | 本アプリの動作には店舗情報のデータを投入する必要があります。 4 | アプリデプロイ時に template.yaml の ItemListDBName に設定したテーブル名のテーブルに、テストデータを投入してください。 5 | テストデータはbackend -> APPフォルダ内、dynamodb_data/table_order_item_0.json ~ table_order_item_3.json, table_order_item_5.jsonの json 形式文字列です。 6 | AWSマネジメントコンソールの DynamoDB コンソールにて、ペーストして投入します。(※以下画像参照) 7 | 8 | 【テストデータの投入】 9 | ![データの投入画像](../images/jp/test-data-charge.png) 10 | 11 | [次の頁へ](validation.md) 12 | 13 | [目次へ戻る](../../README.md) 14 | -------------------------------------------------------------------------------- /docs/jp/validation.md: -------------------------------------------------------------------------------- 1 | # 動作確認 2 | ## LIFFエンドポイントの設定 3 | 【LINEチャネルの作成 -> LIFFアプリの追加】にて作成したLIFFアプリのエンドポイントURLを設定します。 4 | ※ローカル環境構築の場合は、[フロントエンドの開発環境](front-end-development-environment.md)の手順に従い、ローカルのURLを入れてください。 5 | 6 | 1. [LINE Developersコンソール](https://developers.line.biz/console/)にて、【LINEチャネルの作成 -> LIFFアプリの追加】にて作成したLIFFアプリのページに遷移する。 7 | ![LIFFのコンソール](../images/jp/liff-console.png) 8 | 9 | 1. エンドポイントURL項目の編集ボタンを押下する。 10 | ![エンドポイントURLの編集](../images/jp/end-point-url-editing.png) 11 | 12 | 1. 【バックエンドの構築 -> アプリのデプロイ(APP)】の手順にてメモを取ったCloudFrontDomainNameを 先頭にhttps:// を付けて以下のように記載し、更新を押下する。 13 | ![エンドポイントURLの記載](../images/jp/end-point-url-description.png) 14 | 15 | ## リッチメニューの設定 16 | リッチメニューを設定してアプリを起動する場合、以下リンクを参照し設定してください。 17 | https://developers.line.biz/ja/docs/messaging-api/using-rich-menus/#creating-a-rich-menu-with-the-line-manager 18 | 19 | ## 動作確認 20 | 21 | すべての手順が完了後、【LINEチャネルの作成 -> LIFFアプリの追加】の手順にて作成したLIFFアプリのLIFF URLにアクセスし、動作を確認してください。 22 | 23 | ※Cloudfront作成に2時間ほどかかる可能性があります。Access Deniedの画面が表示される場合、一度時間を置いてから確認して下さい。 24 | 25 | [目次へ戻る](../../README.md) 26 | -------------------------------------------------------------------------------- /front/.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 4 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /front/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "es2020": true 5 | }, 6 | "extends": [ 7 | "eslint:recommended", 8 | "plugin:vue/essential" 9 | ], 10 | "parserOptions": { 11 | "ecmaVersion": 11, 12 | "sourceType": "module" 13 | }, 14 | "plugins": [ 15 | "vue" 16 | ], 17 | "rules": { 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /front/.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### Node template 3 | # Logs 4 | /logs 5 | *.log 6 | npm-debug.log* 7 | yarn-debug.log* 8 | yarn-error.log* 9 | 10 | # Runtime data 11 | pids 12 | *.pid 13 | *.seed 14 | *.pid.lock 15 | 16 | # Directory for instrumented libs generated by jscoverage/JSCover 17 | lib-cov 18 | 19 | # Coverage directory used by tools like istanbul 20 | coverage 21 | 22 | # nyc test coverage 23 | .nyc_output 24 | 25 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 26 | .grunt 27 | 28 | # Bower dependency directory (https://bower.io/) 29 | bower_components 30 | 31 | # node-waf configuration 32 | .lock-wscript 33 | 34 | # Compiled binary addons (https://nodejs.org/api/addons.html) 35 | build/Release 36 | 37 | # Dependency directories 38 | node_modules/ 39 | jspm_packages/ 40 | 41 | # TypeScript v1 declaration files 42 | typings/ 43 | 44 | # Optional npm cache directory 45 | .npm 46 | 47 | # Optional eslint cache 48 | .eslintcache 49 | 50 | # Optional REPL history 51 | .node_repl_history 52 | 53 | # Output of 'npm pack' 54 | *.tgz 55 | 56 | # Yarn Integrity file 57 | .yarn-integrity 58 | 59 | # dotenv environment variables file 60 | .env 61 | 62 | # parcel-bundler cache (https://parceljs.org/) 63 | .cache 64 | 65 | # next.js build output 66 | .next 67 | 68 | # nuxt.js build output 69 | .nuxt 70 | 71 | # Nuxt generate 72 | dist 73 | 74 | # vuepress build output 75 | .vuepress/dist 76 | 77 | # Serverless directories 78 | .serverless 79 | 80 | # IDE / Editor 81 | .idea 82 | 83 | # Service worker 84 | sw.* 85 | 86 | # macOS 87 | .DS_Store 88 | 89 | # Vim swap files 90 | *.swp 91 | -------------------------------------------------------------------------------- /front/assets/css/style.css: -------------------------------------------------------------------------------- 1 | .wrap { 2 | overflow: hidden; 3 | } 4 | 5 | /* Vue Transition */ 6 | .v-enter-active { 7 | transition: opacity 1.0s; 8 | } 9 | .v-enter { 10 | opacity: 0; 11 | } 12 | 13 | /* Circle */ 14 | .circle { 15 | border-radius: 50%; 16 | background-position: center; 17 | background-size: cover; 18 | } 19 | 20 | /* Processing */ 21 | .loader-wrap { 22 | display: none; 23 | position:fixed; 24 | left: 0; 25 | top: 0; 26 | z-index: 99999; 27 | width: 100%; 28 | height: 100%; 29 | padding-top: 50px; 30 | background:rgba(0, 0, 0, 0.7); 31 | } 32 | .loader { 33 | position: absolute; 34 | left: calc(50% - 32px); 35 | top: calc(50% - 32px); 36 | width: 64px; 37 | height: 64px; 38 | perspective: 800px; 39 | } 40 | .loader-message { 41 | position: absolute; 42 | left: calc(50% - 250px); 43 | top: calc(50% + 64px); 44 | width: 500px; 45 | height: 64px; 46 | color: #fff; 47 | text-align: center; 48 | } 49 | .inner { 50 | position: absolute; 51 | box-sizing: border-box; 52 | width: 100%; 53 | height: 100%; 54 | border-radius: 50%; 55 | } 56 | .inner.one { 57 | left: 0%; 58 | top: 0%; 59 | animation: rotate-one 1s linear infinite; 60 | border-bottom: 3px solid #EFEFFA; 61 | } 62 | .inner.two { 63 | right: 0%; 64 | top: 0%; 65 | animation: rotate-two 1s linear infinite; 66 | border-right: 3px solid #EFEFFA; 67 | } 68 | .inner.three { 69 | right: 0%; 70 | bottom: 0%; 71 | animation: rotate-three 1s linear infinite; 72 | border-top: 3px solid #EFEFFA; 73 | } 74 | 75 | /* word-break */ 76 | .v-card__title { 77 | word-break: normal !important; 78 | } 79 | 80 | @keyframes rotate-one { 81 | 0% { 82 | transform: rotateX(35deg) rotateY(-45deg) rotateZ(0deg); 83 | } 84 | 100% { 85 | transform: rotateX(35deg) rotateY(-45deg) rotateZ(360deg); 86 | } 87 | } 88 | @keyframes rotate-two { 89 | 0% { 90 | transform: rotateX(50deg) rotateY(10deg) rotateZ(0deg); 91 | } 92 | 100% { 93 | transform: rotateX(50deg) rotateY(10deg) rotateZ(360deg); 94 | } 95 | } 96 | @keyframes rotate-three { 97 | 0% { 98 | transform: rotateX(35deg) rotateY(55deg) rotateZ(0deg); 99 | } 100 | 100% { 101 | transform: rotateX(35deg) rotateY(55deg) rotateZ(360deg); 102 | } 103 | } 104 | 105 | /* LINE Color */ 106 | .line-color { 107 | color: #00ba00; 108 | } 109 | .line-border-color { 110 | border-color: #00ba00; 111 | } 112 | .line-background-color { 113 | background-color: #00ba00; 114 | } 115 | -------------------------------------------------------------------------------- /front/assets/img/line.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/line/line-api-use-case-table-order/40f420b8e6e4f1a90ef65c3fd26b9ef1c0555ef9/front/assets/img/line.png -------------------------------------------------------------------------------- /front/assets/img/line_pay.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/line/line-api-use-case-table-order/40f420b8e6e4f1a90ef65c3fd26b9ef1c0555ef9/front/assets/img/line_pay.png -------------------------------------------------------------------------------- /front/assets/img/line_pay2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/line/line-api-use-case-table-order/40f420b8e6e4f1a90ef65c3fd26b9ef1c0555ef9/front/assets/img/line_pay2.png -------------------------------------------------------------------------------- /front/assets/sass/_variables.scss: -------------------------------------------------------------------------------- 1 | // Body 2 | $body-bg: #f8fafc; 3 | 4 | // Typography 5 | $font-family-sans-serif: 'Nunito', sans-serif; 6 | $font-size-base: 0.9rem; 7 | $line-height-base: 1.6; 8 | 9 | // Colors 10 | $blue: #3490dc; 11 | $indigo: #6574cd; 12 | $purple: #9561e2; 13 | $pink: #f66d9b; 14 | $red: #e3342f; 15 | $orange: #f6993f; 16 | $yellow: #ffed4a; 17 | $green: #38c172; 18 | $teal: #4dc0b5; 19 | $cyan: #6cb2eb; 20 | -------------------------------------------------------------------------------- /front/assets/sass/app.scss: -------------------------------------------------------------------------------- 1 | // Variables 2 | @import "variables"; 3 | -------------------------------------------------------------------------------- /front/cert/ここにSSL証明書のcrtファイル、 keyファイルを配置してください: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/line/line-api-use-case-table-order/40f420b8e6e4f1a90ef65c3fd26b9ef1c0555ef9/front/cert/ここにSSL証明書のcrtファイル、 keyファイルを配置してください -------------------------------------------------------------------------------- /front/components/ErrorModal.vue: -------------------------------------------------------------------------------- 1 | 35 | -------------------------------------------------------------------------------- /front/components/tableorder/Header.vue: -------------------------------------------------------------------------------- 1 | 27 | 28 | 96 | -------------------------------------------------------------------------------- /front/components/tableorder/MenuCard.vue: -------------------------------------------------------------------------------- 1 | 76 | -------------------------------------------------------------------------------- /front/components/tableorder/Ordered.vue: -------------------------------------------------------------------------------- 1 | 47 | 48 | 84 | 85 | 90 | -------------------------------------------------------------------------------- /front/layouts/default.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 63 | -------------------------------------------------------------------------------- /front/layouts/tableorder/order.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 42 | 43 | 62 | -------------------------------------------------------------------------------- /front/locales/ja.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "ja", 3 | "language": "日本語", 4 | "title": "Use Case テーブルオーダー", 5 | "top": { 6 | "title": "Use Case テーブルオーダー", 7 | "msg001": "飲食店内でLIFFアプリから商品の注文と決済ができます。", 8 | "msg002": "居酒屋のように最後に一括で決済する場合と、
フードコートのように都度決済する場合の、どちらにも対応できます。", 9 | "msg003": "テーブルオーダーデモアプリでは、皆さまのLINEアカウントの「プロフィール情報(表示名、ユーザーID)」を取得します。", 10 | "msg004": "ユーザーIDのみシステムに保存いたしますが、保存したデータは毎日削除されます。", 11 | "msg005": "上記をご理解のうえ、ご利用ください。", 12 | "msg006": "ご注文はこちらから" 13 | }, 14 | "menu": { 15 | "msg001": "{name}様", 16 | "msg002": "座席番号", 17 | "msg003": "注文履歴・会計", 18 | "msg004": "バスケットの中身を見る", 19 | "msg005": "点", 20 | "msg006": "{price}円", 21 | "msg007": "個", 22 | "msg008": "バスケットに追加", 23 | "msg009": "座席番号を入力してください", 24 | "msg010": "半角数字", 25 | "msg011": "入力完了", 26 | "msg012": "入力してください", 27 | "msg013": "半角数字で入力してください" 28 | }, 29 | "basket": { 30 | "product": "商品名", 31 | "qty": "個数", 32 | "price": "価格", 33 | "yen": "{price}円", 34 | "total_pretax": "値引前合計", 35 | "total_discount": "値引合計", 36 | "total_amount": "合計金額", 37 | "cancel": "キャンセル", 38 | "msg001": "すべての商品をバスケットから削除", 39 | "msg002": "上記の内容で注文する", 40 | "msg003": "注文を確定します", 41 | "msg004": "よろしければOKボタンを押してください。", 42 | "msg005": "すべての商品を
バスケットから削除してよろしいですか?" 43 | }, 44 | "completed": { 45 | "msg001": "ご注文ありがとうございました。", 46 | "msg002": "商品が到着するまでお待ちください。", 47 | "msg003": "注文履歴・会計に進む" 48 | }, 49 | "payment": { 50 | "msg001": "{price}円 会計を行う", 51 | "msg002": "LINE Payでの支払いはLINE Payを加入する必要があります。", 52 | "msg003": "本デモアプリが用意したLINE Pay残高から決済が行われます。", 53 | "msg004": "実際の決済手段の選択および決済は行われません。", 54 | "msg005": "注文済み", 55 | "msg006": "支払い方法の選択", 56 | "msg007": "合計 {price}円", 57 | "msg008": "残高不足と表示された場合は、
しばらく待ってから決済してください。", 58 | "msg009": "レジで決済", 59 | "msg010": "LINE Payを呼び出しています..", 60 | "msg011": "スタッフを呼び出し、決済手続きをしています.." 61 | }, 62 | "paymentCompleted": { 63 | "title": "Use Case テーブルオーダー", 64 | "msg001": "決済が完了しました。", 65 | "msg002": "またのご利用をお待ちしております。", 66 | "msg003": "TOPへ戻る" 67 | }, 68 | "header": { 69 | "msg001": "{name}様", 70 | "msg002": "座席番号" 71 | }, 72 | "menucard": { 73 | "yen": "{price}円" 74 | }, 75 | "ordered": { 76 | "yen": "{price}円", 77 | "msg001": "商品名", 78 | "msg002": "単価(値引後)", 79 | "msg003": "個数", 80 | "msg004": "価格", 81 | "msg005": "合計金額(税込)" 82 | }, 83 | "utils": { 84 | "sun": "日", 85 | "mon": "月", 86 | "tue": "火", 87 | "wed": "水", 88 | "thu": "木", 89 | "fri": "金", 90 | "sat": "土", 91 | "hol": "祝" 92 | }, 93 | "tableorder": { 94 | 95 | }, 96 | "error": { 97 | "msg001": "通信に失敗しました", 98 | "msg002": "リロードして再試行してもエラーが解消しない場合は、
お手数ですが画面を閉じてからもう一度操作をやり直してください。", 99 | "msg003": "エラー内容", 100 | "msg004": "リロードして再試行", 101 | "msg005": "決済に失敗しました", 102 | "msg006": "お手数ですが会計前の画面に戻り、
もう一度LINE Pay決済をお試しください。", 103 | "msg007": "会計前の画面に戻る", 104 | "msg008": "セッションが切れました。再度最初から操作してください。" 105 | } 106 | } -------------------------------------------------------------------------------- /front/middleware/initialize.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 初期処理ミドルウェア 3 | * 4 | * @param {Object} context 5 | */ 6 | export default async (context) => { 7 | /** @type {boolean} 初期化済フラグ */ 8 | const inited = context.app.$flash.hold("LIFF_INITED"); 9 | 10 | /** 11 | * LIFFプロファイル取得・設定 12 | * 13 | */ 14 | const _settingLiffProfile = async() => { 15 | const lineUser = await context.app.$liff.getLiffProfile(); 16 | context.store.commit("lineUser", lineUser); 17 | } 18 | 19 | // LIFF Login & Profile 20 | if (inited) { 21 | const lineUser = context.store.state.lineUser; 22 | if (!lineUser || !("expire" in lineUser)) { 23 | // Get LIFF Profile & Token 24 | _settingLiffProfile(); 25 | } else { 26 | const now = new Date(); 27 | const expire = parseInt(lineUser.expire, 10); 28 | if (expire < now.getTime()) { 29 | // Get LIFF Profile & Token 30 | _settingLiffProfile(); 31 | } 32 | } 33 | } else { 34 | // 起動時間 35 | context.store.commit("started", new Date().toLocaleString({ timeZone: "Asia/Tokyo" })); 36 | // 言語 37 | if ("lang" in context.query) { 38 | context.store.commit("locale", context.query.lang); 39 | context.app.i18n.locale = context.store.state.locale; 40 | } else if (context.store.state.locale) { 41 | context.app.i18n.locale = context.store.state.locale; 42 | } 43 | 44 | // LIFF Initialize 45 | liff.init({ liffId: context.env.LIFF_ID }) 46 | .then(() => { 47 | context.app.$flash.set("LIFF_INITED", true); 48 | const loggedIn = liff.isLoggedIn(); 49 | if (!loggedIn) { 50 | liff.login(); 51 | } 52 | }) 53 | .catch((err) => { 54 | console.log(err); 55 | }); 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /front/nuxt.config.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs' 2 | import dotenv from 'dotenv' 3 | 4 | dotenv.config(); 5 | 6 | export default { 7 | telemetry: false, 8 | mode: 'spa', 9 | generate: { 10 | dir: './dist' 11 | }, 12 | server: { 13 | port: 3000, 14 | host: '0.0.0.0', 15 | timing: false, 16 | https: { 17 | // key: fs.readFileSync('./cert/localhost.key'), 18 | // cert: fs.readFileSync('./cert/localhost.crt') 19 | } 20 | }, 21 | target: 'server', 22 | router: { 23 | extendRoutes(routes, resolve) { 24 | routes.push({ 25 | name: "notfound", 26 | path: "*", 27 | component: resolve(__dirname, "pages/error/404.vue") 28 | }); 29 | } 30 | }, 31 | head: { 32 | title: 'Use Case Application', 33 | meta: [ 34 | { charset: 'utf-8' }, 35 | { name: 'viewport', content: 'width=device-width, initial-scale=1' }, 36 | { hid: 'description', name: 'description', content: process.env.npm_package_description || '' } 37 | ], 38 | link: [ 39 | { rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }, 40 | ], 41 | script: [ 42 | { src: 'https://static.line-scdn.net/liff/edge/versions/2.5.0/sdk.js' }, 43 | ], 44 | }, 45 | loading: { 46 | color: '#00ba00', 47 | }, 48 | css: [ 49 | 50 | ], 51 | plugins: [ 52 | { src: '~/plugins/i18n.js', mode: 'client' }, 53 | { src: '~/plugins/localStorage.js', mode: 'client' }, 54 | { src: '~/plugins/sessionStorage.js', mode: 'client' }, 55 | { src: '~/plugins/axiosEx.js', mode: 'client' }, 56 | { src: '~/plugins/amplify.js', mode: 'client' }, 57 | { src: '~/plugins/vuetify.js', mode: 'client' }, 58 | { src: '~/plugins/noreload.js', mode: 'client' }, 59 | { src: '~/plugins/flashMessage.js', mode: 'client' }, 60 | { src: '~/plugins/processing.js', mode: 'client' }, 61 | { src: '~/plugins/utils.js', mode: 'client' }, 62 | { src: '~/plugins/liff.js', mode: 'client' }, 63 | { src: '~/plugins/app/tableorder.js', mode: 'client' }, 64 | ], 65 | components: true, 66 | buildModules: [ 67 | '@nuxtjs/vuetify', 68 | ], 69 | modules: [ 70 | '@nuxtjs/axios', 71 | '@nuxtjs/dotenv', 72 | ], 73 | build: { 74 | }, 75 | axios: { 76 | baseURL: process.env.BASE_URL, 77 | }, 78 | env: { 79 | LIFF_ID: process.env.LIFF_ID, 80 | APIGATEWAY_STAGE: process.env.APIGATEWAY_STAGE, 81 | AJAX_MODULE: process.env.AJAX_MODULE, 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /front/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nuxt", 3 | "version": "1.0.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "nuxt", 7 | "build": "nuxt build", 8 | "start": "nuxt start", 9 | "export": "nuxt export", 10 | "serve": "nuxt serve" 11 | }, 12 | "dependencies": { 13 | "@nuxtjs/axios": "^5.13.1", 14 | "@nuxtjs/dotenv": "1.4.1", 15 | "@aws-amplify/ui-vue": "0.2.12", 16 | "aws-amplify": "^3.3.22", 17 | "aws-amplify-vue": "2.1.2", 18 | "vue-i18n": "8.22.2", 19 | "nuxt": "2.13.3" 20 | }, 21 | "devDependencies": { 22 | "@babel/preset-env": "7.12.17", 23 | "@fortawesome/fontawesome-free": "5.14.0", 24 | "@fortawesome/fontawesome-svg-core": "1.2.30", 25 | "@fortawesome/free-solid-svg-icons": "5.14.0", 26 | "@fortawesome/vue-fontawesome": "0.1.10", 27 | "@mdi/font": "5.5.55", 28 | "@mdi/js": "5.5.55", 29 | "@nuxtjs/vuetify": "1.11.2", 30 | "bootstrap": "4.5.0", 31 | "eslint": "7.6.0", 32 | "eslint-plugin-vue": "6.2.2", 33 | "font-awesome": "4.7.0", 34 | "material-design-icons-iconfont": "5.0.1", 35 | "node-sass": "4.14.1", 36 | "sass-loader": "9.0.2", 37 | "vuedraggable": "2.24.1", 38 | "vuex-persistedstate": "3.0.1" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /front/pages/error/404.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 30 | 31 | 46 | -------------------------------------------------------------------------------- /front/pages/index.vue: -------------------------------------------------------------------------------- 1 | 26 | 27 | 56 | 57 | 64 | -------------------------------------------------------------------------------- /front/pages/tableorder/completed.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 68 | -------------------------------------------------------------------------------- /front/pages/tableorder/payment.vue: -------------------------------------------------------------------------------- 1 | 60 | 61 | 183 | 184 | 197 | -------------------------------------------------------------------------------- /front/pages/tableorder/paymentCompleted.vue: -------------------------------------------------------------------------------- 1 | 26 | -------------------------------------------------------------------------------- /front/plugins/amplify.js: -------------------------------------------------------------------------------- 1 | import Amplify from "aws-amplify"; 2 | import API from "@aws-amplify/api"; 3 | 4 | // Amplify設定 5 | Amplify['API'] = API; 6 | Amplify.configure({ 7 | API: { 8 | endpoints: [ 9 | { 10 | name: "LambdaAPIGateway", 11 | endpoint: process.env.BASE_URL, 12 | }, 13 | ] 14 | } 15 | }); 16 | 17 | /** 18 | * Amplifyプラグイン 19 | * 20 | * @return {Object} 21 | */ 22 | const VueAmplify = () => { 23 | return Amplify; 24 | } 25 | 26 | export default ({}, inject) => { 27 | inject("amplify", VueAmplify()); 28 | } 29 | -------------------------------------------------------------------------------- /front/plugins/axiosEx.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Axios拡張プラグイン 3 | * 4 | * @param {Object} $axios 5 | * @param {Object} store 6 | */ 7 | export default({ $axios, app, store }) => { 8 | // リクエスト拡張 9 | $axios.onRequest((config)=>{ 10 | 11 | }); 12 | // レスポンス拡張 13 | $axios.onResponse((response)=>{ 14 | 15 | }); 16 | // エラー拡張 17 | $axios.onError((error)=>{ 18 | return app.$utils.showHttpError(error); 19 | }); 20 | } 21 | -------------------------------------------------------------------------------- /front/plugins/flashMessage.js: -------------------------------------------------------------------------------- 1 | import Vue from "vue" 2 | 3 | /** 4 | * フラッシュメッセージプラグイン 5 | * 6 | * @return {Object} 7 | */ 8 | const VueFlashMessage = () => { 9 | 10 | let _message = {}; 11 | 12 | return { 13 | get(name) { 14 | if (name in _message) { 15 | const ret = Vue.util.extend(_message[name]); 16 | delete _message[name]; 17 | return ret; 18 | } 19 | return undefined; 20 | }, 21 | set(name, value) { 22 | _message[name] = value; 23 | }, 24 | hold(name) { 25 | return _message[name]; 26 | }, 27 | clear(name) { 28 | if (name === undefined) { 29 | _message = {}; 30 | } else if (name in _message) { 31 | delete _message[name]; 32 | } 33 | } 34 | }; 35 | } 36 | 37 | export default ({}, inject) => { 38 | inject("flash", VueFlashMessage()); 39 | } 40 | -------------------------------------------------------------------------------- /front/plugins/i18n.js: -------------------------------------------------------------------------------- 1 | import Vue from "vue" 2 | import VueI18n from "vue-i18n" 3 | 4 | Vue.use(VueI18n) 5 | 6 | /** 7 | * 多言語化プラグイン 8 | * 9 | * @param {Object} app 10 | * @param {Object} store 11 | */ 12 | export default ({ app, store }) => { 13 | app.i18n = new VueI18n({ 14 | locale: store.state.locale, 15 | fallbackLocale: "ja", 16 | messages: { 17 | ja: require("~/locales/ja.json") 18 | } 19 | }) 20 | } 21 | -------------------------------------------------------------------------------- /front/plugins/liff.js: -------------------------------------------------------------------------------- 1 | /** 2 | * LIFFプラグイン 3 | * 4 | * @param {Object} context 5 | * @return {Object} 6 | */ 7 | const VueLiff = (context) => { 8 | 9 | return { 10 | /** 11 | * LIFF初期化 12 | * 13 | * @param {Function} callback コールバック関数 14 | * @return {any} 戻り値 15 | */ 16 | async init(callback) { 17 | await liff.init({ liffId: context.env.LIFF_ID }); 18 | if (!liff.isLoggedIn()) { 19 | context.redirect("/"); 20 | } else { 21 | return callback(); 22 | } 23 | }, 24 | /** 25 | * LIFFプロファイル取得 26 | * 27 | * @return {Object} LIFFプロファイル情報 28 | */ 29 | async getLiffProfile() { 30 | // LIFF Profile 31 | const profilePromise = liff.getProfile(); 32 | const tokenPromise = liff.getAccessToken(); 33 | const idTokenPromise = liff.getIDToken(); 34 | const profile = await profilePromise; 35 | const token = await tokenPromise; 36 | const idToken = await idTokenPromise; 37 | 38 | const lineUser = { 39 | expire: (new Date()).getTime() + (1000 * 60 * 30), 40 | userId: profile.userId, 41 | name: profile.displayName, 42 | image: profile.pictureUrl, 43 | token: token, 44 | idToken: idToken, 45 | }; 46 | 47 | return lineUser; 48 | }, 49 | 50 | } 51 | } 52 | 53 | export default (context, inject) => { 54 | inject("liff", VueLiff(context)); 55 | } 56 | -------------------------------------------------------------------------------- /front/plugins/localStorage.js: -------------------------------------------------------------------------------- 1 | import CreatePersistedState from "vuex-persistedstate" 2 | 3 | /** 4 | * ローカルストレージプラグイン 5 | * 6 | * @param {Object} env 7 | * @param {Object} store 8 | */ 9 | export default ({ env, store }) => { 10 | CreatePersistedState({ 11 | key: "liff-usecase", 12 | paths: [ 13 | 'started', 14 | 'locale', 15 | ], 16 | storage: window.localStorage 17 | })(store); 18 | } 19 | -------------------------------------------------------------------------------- /front/plugins/noreload.js: -------------------------------------------------------------------------------- 1 | /** 2 | * F5、右クリック無効化プラグイン 3 | * 4 | * @param {Object} store 5 | */ 6 | export default ({store}) => { 7 | if (process.env.NODE_ENV != "production") { return; } 8 | let ctlKey = false; 9 | window.document.addEventListener("keydown", (e)=>{ 10 | if (e.ctrlKey) ctlKey = true; 11 | if ((e.which || e.keyCode) == 82 && ctlKey) e.preventDefault(); 12 | if ((e.which || e.keyCode) == 116) e.preventDefault(); 13 | }, false); 14 | window.document.addEventListener("keyup", (e)=>{ 15 | if (e.ctrlKey) ctlKey = false; 16 | }, false); 17 | window.document.addEventListener("contextmenu", (e)=>{ 18 | e.preventDefault(); 19 | }, false); 20 | } 21 | -------------------------------------------------------------------------------- /front/plugins/processing.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 処理中画面表示プラグイン 3 | * 4 | * @return {Object} 5 | */ 6 | const VueProcessing = () => { 7 | const _id = "_id_loading_component"; 8 | const _default = 0; 9 | const _svg = "PCEtLSBCeSBTYW0gSGVyYmVydCAoQHNoZXJiKSwgZm9yIGV2ZXJ5b25lLiBNb3JlIEAgaHR0cDovL2dvby5nbC83QUp6YkwgLS0+DQo8c3ZnIHdpZHRoPSI0NCIgaGVpZ2h0PSI0NCIgdmlld0JveD0iMCAwIDQ0IDQ0IiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHN0cm9rZT0iI2ZmZiI+DQogICAgPGcgZmlsbD0ibm9uZSIgZmlsbC1ydWxlPSJldmVub2RkIiBzdHJva2Utd2lkdGg9IjIiPg0KICAgICAgICA8Y2lyY2xlIGN4PSIyMiIgY3k9IjIyIiByPSIxIj4NCiAgICAgICAgICAgIDxhbmltYXRlIGF0dHJpYnV0ZU5hbWU9InIiDQogICAgICAgICAgICAgICAgYmVnaW49IjBzIiBkdXI9IjEuOHMiDQogICAgICAgICAgICAgICAgdmFsdWVzPSIxOyAyMCINCiAgICAgICAgICAgICAgICBjYWxjTW9kZT0ic3BsaW5lIg0KICAgICAgICAgICAgICAgIGtleVRpbWVzPSIwOyAxIg0KICAgICAgICAgICAgICAgIGtleVNwbGluZXM9IjAuMTY1LCAwLjg0LCAwLjQ0LCAxIg0KICAgICAgICAgICAgICAgIHJlcGVhdENvdW50PSJpbmRlZmluaXRlIiAvPg0KICAgICAgICAgICAgPGFuaW1hdGUgYXR0cmlidXRlTmFtZT0ic3Ryb2tlLW9wYWNpdHkiDQogICAgICAgICAgICAgICAgYmVnaW49IjBzIiBkdXI9IjEuOHMiDQogICAgICAgICAgICAgICAgdmFsdWVzPSIxOyAwIg0KICAgICAgICAgICAgICAgIGNhbGNNb2RlPSJzcGxpbmUiDQogICAgICAgICAgICAgICAga2V5VGltZXM9IjA7IDEiDQogICAgICAgICAgICAgICAga2V5U3BsaW5lcz0iMC4zLCAwLjYxLCAwLjM1NSwgMSINCiAgICAgICAgICAgICAgICByZXBlYXRDb3VudD0iaW5kZWZpbml0ZSIgLz4NCiAgICAgICAgPC9jaXJjbGU+DQogICAgICAgIDxjaXJjbGUgY3g9IjIyIiBjeT0iMjIiIHI9IjEiPg0KICAgICAgICAgICAgPGFuaW1hdGUgYXR0cmlidXRlTmFtZT0iciINCiAgICAgICAgICAgICAgICBiZWdpbj0iLTAuOXMiIGR1cj0iMS44cyINCiAgICAgICAgICAgICAgICB2YWx1ZXM9IjE7IDIwIg0KICAgICAgICAgICAgICAgIGNhbGNNb2RlPSJzcGxpbmUiDQogICAgICAgICAgICAgICAga2V5VGltZXM9IjA7IDEiDQogICAgICAgICAgICAgICAga2V5U3BsaW5lcz0iMC4xNjUsIDAuODQsIDAuNDQsIDEiDQogICAgICAgICAgICAgICAgcmVwZWF0Q291bnQ9ImluZGVmaW5pdGUiIC8+DQogICAgICAgICAgICA8YW5pbWF0ZSBhdHRyaWJ1dGVOYW1lPSJzdHJva2Utb3BhY2l0eSINCiAgICAgICAgICAgICAgICBiZWdpbj0iLTAuOXMiIGR1cj0iMS44cyINCiAgICAgICAgICAgICAgICB2YWx1ZXM9IjE7IDAiDQogICAgICAgICAgICAgICAgY2FsY01vZGU9InNwbGluZSINCiAgICAgICAgICAgICAgICBrZXlUaW1lcz0iMDsgMSINCiAgICAgICAgICAgICAgICBrZXlTcGxpbmVzPSIwLjMsIDAuNjEsIDAuMzU1LCAxIg0KICAgICAgICAgICAgICAgIHJlcGVhdENvdW50PSJpbmRlZmluaXRlIiAvPg0KICAgICAgICA8L2NpcmNsZT4NCiAgICA8L2c+DQo8L3N2Zz4="; 10 | 11 | const _loadingHtml = (type, message) => { 12 | let div = ""; 13 | message = (typeof(message)=="undefined") ? "" : message; 14 | 15 | switch (type) { 16 | case 0: 17 | div = ` 18 |
19 |
20 |
21 |
22 |
23 |
24 |
${message}
25 |
`; 26 | break; 27 | case 1: 28 | div = ` 29 |
30 |
31 | ${message} 32 |
33 |
${message}
34 |
`; 35 | break; 36 | } 37 | 38 | return div; 39 | } 40 | 41 | 42 | return { 43 | show(type, message) { 44 | type = (typeof(type)=="undefined") ? _default : type; 45 | let div = _loadingHtml(type, message); 46 | let element = document.createElement("div"); 47 | element.innerHTML = div; 48 | document.body.appendChild(element); 49 | let elements = document.getElementsByClassName("loader-wrap"); 50 | for (let element of elements) { 51 | element.style.display = "block"; 52 | } 53 | }, 54 | hide() { 55 | try { document.getElementById(_id).remove(); } catch (ex) {} 56 | } 57 | } 58 | } 59 | 60 | export default ({}, inject) => { 61 | inject("processing", VueProcessing()); 62 | } 63 | -------------------------------------------------------------------------------- /front/plugins/sessionStorage.js: -------------------------------------------------------------------------------- 1 | import CreatePersistedState from "vuex-persistedstate" 2 | 3 | /** 4 | * セッションストレージプラグイン 5 | * 6 | * @param {Object} env 7 | * @param {Object} store 8 | */ 9 | export default ({ env, store }) => { 10 | CreatePersistedState({ 11 | key: "liff-usecase", 12 | paths: [ 13 | 'sessionId', 14 | 'lineUser', 15 | 'customer', 16 | 'orders', 17 | 'ordered', 18 | 'paymentId', 19 | ], 20 | storage: window.sessionStorage // 有効期限はブラウザ閉じるまで(複数ブラウザ立ち上げは別セッションとなる) 21 | })(store); 22 | } 23 | -------------------------------------------------------------------------------- /front/plugins/vuetify.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Vuetify Awesomeフォントプラグイン 3 | * 4 | */ 5 | import "vuetify/dist/vuetify.min.css" 6 | import '@mdi/font/css/materialdesignicons.css' 7 | import 'material-design-icons-iconfont/dist/material-design-icons.css' 8 | import '@fortawesome/fontawesome-free/css/all.css' 9 | import 'font-awesome/css/font-awesome.min.css' 10 | import { library } from '@fortawesome/fontawesome-svg-core' 11 | import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome' 12 | import { fas } from '@fortawesome/free-solid-svg-icons' 13 | 14 | import Vue from "vue" 15 | import Vuetify from "vuetify/lib" 16 | 17 | Vue.use(Vuetify) 18 | library.add(fas) 19 | 20 | export default new Vuetify({ 21 | icons: { 22 | iconfont: "mdi" || "mdiSvg" || "md" || "fa" || "fa4" || "faSvg" 23 | }, 24 | }) 25 | -------------------------------------------------------------------------------- /front/static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/line/line-api-use-case-table-order/40f420b8e6e4f1a90ef65c3fd26b9ef1c0555ef9/front/static/favicon.ico -------------------------------------------------------------------------------- /front/store/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Store 3 | * 4 | */ 5 | export const state = ()=>({ 6 | started: null, 7 | locales: ['ja'], 8 | locale: 'ja', 9 | sessionId: null, 10 | lineUser: null, 11 | customer: null, 12 | orders: null, 13 | ordered: null, 14 | paymentId: null, 15 | axiosError: null, 16 | paymentError: null, 17 | }); 18 | 19 | export const mutations = { 20 | clear(state) { 21 | state.started = null; 22 | state.locale = 'ja'; 23 | state.sessionId = null; 24 | state.lineUser = null; 25 | state.customer = null; 26 | state.orders = null; 27 | state.ordered = null; 28 | state.paymentId = null; 29 | state.axiosError = null; 30 | state.paymentError = null; 31 | }, 32 | started(state, started) { 33 | state.started = started; 34 | }, 35 | locale(state, locale) { 36 | if (state.locales.includes(locale)) { 37 | state.locale = locale; 38 | } 39 | }, 40 | session(state, sessionId) { 41 | state.sessionId = sessionId; 42 | }, 43 | lineUser(state, lineUser) { 44 | state.lineUser = lineUser; 45 | }, 46 | customer(state, customer) { 47 | state.customer = customer; 48 | }, 49 | orders(state, orders) { 50 | state.orders = orders; 51 | }, 52 | ordered(state, ordered) { 53 | state.ordered = ordered; 54 | }, 55 | paymentId(state, paymentId) { 56 | state.paymentId = paymentId; 57 | }, 58 | axiosError(state, axiosError) { 59 | state.axiosError = axiosError; 60 | }, 61 | paymentError(state, paymentError) { 62 | state.paymentError = paymentError; 63 | }, 64 | }; 65 | 66 | export const getters = { 67 | axiosError(state) { 68 | return state.axiosError; 69 | }, 70 | isAxiosError(state) { 71 | return (state.axiosError!=null && !state.paymentError) ? true : false; 72 | }, 73 | isPaymentError(state) { 74 | return state.paymentError; 75 | } 76 | } --------------------------------------------------------------------------------