Replaces MongoDB support with PostgreSQL (#151)
- All schemas have been rewritten to a relational model - All services have been rewritten to accommodate the new data model - Adds idempotency keys to core endpoints allowing you to retry requests with no additional side effects - Adds staged jobs to avoid putting jobs in the queue when transactions abort - Adds atomic transactions to all methods with access to the data layer Co-authored-by: Oliver Windall Juhl <oliver@mrbltech.com>
This commit is contained in:
1 parent
5f819486fc
commit
f1baca3cbd
499 files changed
+25909
-16128
No files matched your search
@@ -7,12 +7,12 @@ routes:
|
||||
path: /orders/:id
|
||||
route: /orders
|
||||
description: >
|
||||
An order represents a selection of items purchased and holds information about
|
||||
An Order represents a selection of items purchased and holds information about
|
||||
how the items have been purchased and will be fulfilled.
|
||||
endpoints:
|
||||
- path: /:id
|
||||
method: GET
|
||||
title: Retrieve an order
|
||||
title: Retrieve an Order
|
||||
params:
|
||||
- name: id
|
||||
type: String
|
||||
@@ -34,104 +34,104 @@ response: |
|
||||
{
|
||||
"order": {
|
||||
"id": "order_fi13oadpo2r3vc2g4592",
|
||||
"status": "pending",
|
||||
"fulfillment_status": "shipped",
|
||||
"payment_status": "captured",
|
||||
"email": "iron@man.com",
|
||||
"billing_address": {
|
||||
"id": "addr_WgNn0BSfIu",
|
||||
"customer_id": "cus_4eThzYSuGv",
|
||||
"company": "Stark Industries",
|
||||
"first_name": "Tony",
|
||||
"last_name": "Stark",
|
||||
"address_1": "Hollywood Boulevard 1",
|
||||
"address_2": null,
|
||||
"city": "Los Angeles",
|
||||
"country_code": "US",
|
||||
"province": "CA",
|
||||
"postal_code": "90046",
|
||||
"phone": null,
|
||||
"created_at": "2020-12-11T17:03:54.458Z",
|
||||
"updated_at": "2020-12-11T17:03:54.458Z",
|
||||
"deleted_at": null,
|
||||
"metadata": null
|
||||
},
|
||||
"shipping_address": {
|
||||
"id": "addr_WgNn0BSfIu",
|
||||
"customer_id": "cus_4eThzYSuGv",
|
||||
"company": "Stark Industries",
|
||||
"first_name": "Tony",
|
||||
"last_name": "Stark",
|
||||
"address_1": "Hollywood Boulevard 1",
|
||||
"address_2": null,
|
||||
"city": "Los Angeles",
|
||||
"country_code": "US",
|
||||
"province": "CA",
|
||||
"postal_code": "90046",
|
||||
"phone": null,
|
||||
"created_at": "2020-12-11T17:03:54.458Z",
|
||||
"updated_at": "2020-12-11T17:03:54.458Z",
|
||||
"deleted_at": null,
|
||||
"metadata": null
|
||||
},
|
||||
"items": [
|
||||
{
|
||||
"id": "item_fn2uaQH95vG9ZMnhj2aU03xg",
|
||||
"cart_id": null,
|
||||
"order_id": "order_s9RojwCU2AM8RztcldM2Uof7",
|
||||
"swap_id": null,
|
||||
"title": "Ironman suit",
|
||||
"description": "Awesome Ironman suit",
|
||||
"thumbnail": null,
|
||||
"is_giftcard": false,
|
||||
"should_merge": false,
|
||||
"allow_discounts": true,
|
||||
"unit_price": 119600,
|
||||
"variant_id": "variant_rdEH6PykBuH57giw",
|
||||
"quantity": 1,
|
||||
"fulfilled_quantity": 1,
|
||||
"returned_quantity": 0,
|
||||
"shipped_quantity": 0,
|
||||
"created_at": "2020-12-11T17:03:54.458Z",
|
||||
"updated_at": "2020-12-11T17:03:54.458Z",
|
||||
"metadata": null
|
||||
}
|
||||
],
|
||||
"discounts": [
|
||||
{
|
||||
"id": "disc_OpYQN4H8MOWHN2en",
|
||||
"code": "CCC3C4LL88",
|
||||
"is_dynamic": true,
|
||||
"discount_rule_id": "dru_MDyr3lJLa00uxOsY",
|
||||
"parent_discount_id": "disc_ubtdlkASI7bfUj81",
|
||||
"metadata": null
|
||||
}
|
||||
],
|
||||
"customer_id": "cus_4eThzYSuGv",
|
||||
"shipping_methods": [
|
||||
{
|
||||
"id": "sm_77bEapbO8tkCqw3yo1NBuCUl",
|
||||
"shipping_option_id": "so_nRvwHhEdZw",
|
||||
"order_id": "order_s9RojwCU2AM8RztcldM2Uof7",
|
||||
"cart_id": null,
|
||||
"swap_id": null,
|
||||
"return_id": null,
|
||||
"price": 0,
|
||||
"data": {
|
||||
"id": "Parcel shop",
|
||||
"city": "Los Angeles",
|
||||
"postal": "90046"
|
||||
}
|
||||
}
|
||||
],
|
||||
"metadata": null,
|
||||
"display_id": 50433,
|
||||
"currency_code": "dkk",
|
||||
"region_id": "reg_HMnixPlOicAs7aBlXuchAGxd",
|
||||
"shipping_total": 0,
|
||||
"discount_total": 0,
|
||||
"tax_total": 3850000,
|
||||
"subtotal": 154000,
|
||||
"total": 4004000
|
||||
"status": "pending",
|
||||
"fulfillment_status": "shipped",
|
||||
"payment_status": "captured",
|
||||
"email": "iron@man.com",
|
||||
"billing_address": {
|
||||
"id": "addr_WgNn0BSfIu",
|
||||
"customer_id": "cus_4eThzYSuGv",
|
||||
"company": "Stark Industries",
|
||||
"first_name": "Tony",
|
||||
"last_name": "Stark",
|
||||
"address_1": "Hollywood Boulevard 1",
|
||||
"address_2": null,
|
||||
"city": "Los Angeles",
|
||||
"country_code": "US",
|
||||
"province": "CA",
|
||||
"postal_code": "90046",
|
||||
"phone": null,
|
||||
"created_at": "2020-12-11T17:03:54.458Z",
|
||||
"updated_at": "2020-12-11T17:03:54.458Z",
|
||||
"deleted_at": null,
|
||||
"metadata": null
|
||||
},
|
||||
"shipping_address": {
|
||||
"id": "addr_WgNn0BSfIu",
|
||||
"customer_id": "cus_4eThzYSuGv",
|
||||
"company": "Stark Industries",
|
||||
"first_name": "Tony",
|
||||
"last_name": "Stark",
|
||||
"address_1": "Hollywood Boulevard 1",
|
||||
"address_2": null,
|
||||
"city": "Los Angeles",
|
||||
"country_code": "US",
|
||||
"province": "CA",
|
||||
"postal_code": "90046",
|
||||
"phone": null,
|
||||
"created_at": "2020-12-11T17:03:54.458Z",
|
||||
"updated_at": "2020-12-11T17:03:54.458Z",
|
||||
"deleted_at": null,
|
||||
"metadata": null
|
||||
},
|
||||
"items": [
|
||||
{
|
||||
"id": "item_fn2uaQH95vG9ZMnhj2aU03xg",
|
||||
"cart_id": null,
|
||||
"order_id": "order_s9RojwCU2AM8RztcldM2Uof7",
|
||||
"swap_id": null,
|
||||
"title": "Ironman suit",
|
||||
"description": "Awesome Ironman suit",
|
||||
"thumbnail": null,
|
||||
"is_giftcard": false,
|
||||
"should_merge": false,
|
||||
"allow_discounts": true,
|
||||
"unit_price": 119600,
|
||||
"variant_id": "variant_rdEH6PykBuH57giw",
|
||||
"quantity": 1,
|
||||
"fulfilled_quantity": 1,
|
||||
"returned_quantity": 0,
|
||||
"shipped_quantity": 0,
|
||||
"created_at": "2020-12-11T17:03:54.458Z",
|
||||
"updated_at": "2020-12-11T17:03:54.458Z",
|
||||
"metadata": null
|
||||
}
|
||||
],
|
||||
"discounts": [
|
||||
{
|
||||
"id": "disc_OpYQN4H8MOWHN2en",
|
||||
"code": "CCC3C4LL88",
|
||||
"is_dynamic": true,
|
||||
"discount_rule_id": "dru_MDyr3lJLa00uxOsY",
|
||||
"parent_discount_id": "disc_ubtdlkASI7bfUj81",
|
||||
"metadata": null
|
||||
}
|
||||
],
|
||||
"customer_id": "cus_4eThzYSuGv",
|
||||
"shipping_methods": [
|
||||
{
|
||||
"id": "sm_77bEapbO8tkCqw3yo1NBuCUl",
|
||||
"shipping_option_id": "so_nRvwHhEdZw",
|
||||
"order_id": "order_s9RojwCU2AM8RztcldM2Uof7",
|
||||
"cart_id": null,
|
||||
"swap_id": null,
|
||||
"return_id": null,
|
||||
"price": 0,
|
||||
"data": {
|
||||
"id": "Parcel shop",
|
||||
"city": "Los Angeles",
|
||||
"postal": "90046"
|
||||
}
|
||||
}
|
||||
],
|
||||
"metadata": null,
|
||||
"display_id": 50433,
|
||||
"currency_code": "dkk",
|
||||
"region_id": "reg_HMnixPlOicAs7aBlXuchAGxd",
|
||||
"shipping_total": 0,
|
||||
"discount_total": 0,
|
||||
"tax_total": 3850000,
|
||||
"subtotal": 154000,
|
||||
"total": 4004000
|
||||
}
|
||||
}
|
||||
+8
-9
@@ -1,10 +1,9 @@
|
||||
0 silly argv { _: [ 'publish' ],
|
||||
0 silly argv composed: 'publish',
|
||||
0 silly argv lernaVersion: '3.20.2',
|
||||
0 silly argv '$0': '/usr/local/bin/lerna',
|
||||
0 silly argv bump: 'from-git' }
|
||||
1 notice cli v3.20.2
|
||||
0 silly argv { _: [ 'bootstrap' ],
|
||||
0 silly argv lernaVersion: '3.22.1',
|
||||
0 silly argv '$0': '/usr/local/bin/lerna' }
|
||||
1 notice cli v3.22.1
|
||||
2 verbose rootPath /Users/srindom/Developer/medusa-js
|
||||
3 error JSONError: Unexpected token : in JSON at position 9 while parsing near ' "name": "@medusajs/medusa"...' in packages/medusa/package.json
|
||||
3 error at module.exports (/Users/srindom/Developer/medusa-js/node_modules/parse-json/index.js:26:19)
|
||||
3 error at parse (/Users/srindom/Developer/medusa-js/node_modules/load-json-file/index.js:15:9)
|
||||
3 info versioning independent
|
||||
4 error JSONError: Unexpected token < in JSON at position 34 while parsing near '...edusa-file-spaces",<<<<<<< HEAD "vers...' in packages/medusa-file-spaces/package.json
|
||||
4 error at module.exports (/Users/srindom/Developer/medusa-js/node_modules/parse-json/index.js:26:19)
|
||||
4 error at parse (/Users/srindom/Developer/medusa-js/node_modules/load-json-file/index.js:15:9)
|
||||
@@ -15,7 +15,10 @@ function preset(context, options = {}) {
|
||||
return {
|
||||
presets: [r(`@babel/preset-env`)],
|
||||
plugins: [
|
||||
r(`@babel/plugin-proposal-class-properties`),
|
||||
r(`babel-plugin-transform-typescript-metadata`),
|
||||
r(`@babel/plugin-proposal-optional-chaining`),
|
||||
[r(`@babel/plugin-proposal-decorators`), { legacy: true }],
|
||||
[r(`@babel/plugin-proposal-class-properties`), { loose: true }],
|
||||
r(`@babel/plugin-transform-classes`),
|
||||
r(`@babel/plugin-transform-instanceof`),
|
||||
r(`@babel/plugin-transform-runtime`),
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "babel-preset-medusa-package",
|
||||
"version": "1.0.1",
|
||||
"version": "1.0.2-alpha.787+0646bd3",
|
||||
"author": "Sebastian Rindom <sebastian@mrbltech.com>",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -9,11 +9,13 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/plugin-proposal-class-properties": "^7.12.1",
|
||||
"@babel/plugin-proposal-decorators": "^7.12.1",
|
||||
"@babel/plugin-proposal-optional-chaining": "^7.12.1",
|
||||
"@babel/plugin-transform-classes": "^7.12.1",
|
||||
"@babel/plugin-transform-instanceof": "^7.12.1",
|
||||
"@babel/plugin-transform-runtime": "^7.12.1",
|
||||
"@babel/preset-env": "^7.12.7",
|
||||
"babel-plugin-transform-typescript-metadata": "^0.3.1",
|
||||
"core-js": "^3.7.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
@@ -23,5 +25,6 @@
|
||||
"main": "index.js",
|
||||
"engines": {
|
||||
"node": ">=10.14.0"
|
||||
}
|
||||
},
|
||||
"gitHead": "0646bd395a6056657cb0aa93c13699c4a9dbbcdd"
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@medusajs/medusa-cli",
|
||||
"version": "1.0.11",
|
||||
"version": "1.0.12-alpha.787+0646bd3",
|
||||
"description": "Command Line interface for Medusa Commerce",
|
||||
"main": "dist/index.js",
|
||||
"bin": {
|
||||
@@ -48,5 +48,5 @@
|
||||
"resolve-cwd": "^3.0.0",
|
||||
"yargs": "^15.3.1"
|
||||
},
|
||||
"gitHead": "3cc7cbe5124cbcbb75f6e1435db4dcfaa2a60408"
|
||||
"gitHead": "0646bd395a6056657cb0aa93c13699c4a9dbbcdd"
|
||||
}
|
||||
@@ -61,6 +61,22 @@ function buildLocalCommands(cli, isLocalProject) {
|
||||
}
|
||||
|
||||
cli
|
||||
.command({
|
||||
command: `migrations [action]`,
|
||||
desc: `Migrate the database to the most recent version.`,
|
||||
builder: {
|
||||
action: {
|
||||
demand: true,
|
||||
choices: ["run", "show"],
|
||||
},
|
||||
},
|
||||
handler: handlerP(
|
||||
getCommandHandler(`migrate`, (args, cmd) => {
|
||||
process.env.NODE_ENV = process.env.NODE_ENV || `development`
|
||||
return cmd(args)
|
||||
})
|
||||
),
|
||||
})
|
||||
.command({
|
||||
command: `develop`,
|
||||
desc: `Start development server. Watches file and rebuilds when something changes`,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "medusa-core-utils",
|
||||
"version": "1.0.11",
|
||||
"version": "1.0.12-alpha.787+0646bd3",
|
||||
"description": "Core utils for Medusa",
|
||||
"main": "dist/index.js",
|
||||
"repository": {
|
||||
@@ -30,8 +30,8 @@
|
||||
"prettier": "^1.19.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"joi": "^17.2.1",
|
||||
"joi": "^17.3.0",
|
||||
"joi-objectid": "^3.0.1"
|
||||
},
|
||||
"gitHead": "3cc7cbe5124cbcbb75f6e1435db4dcfaa2a60408"
|
||||
"gitHead": "0646bd395a6056657cb0aa93c13699c4a9dbbcdd"
|
||||
}
|
||||
@@ -7,13 +7,13 @@ Joi.address = () => {
|
||||
first_name: Joi.string().required(),
|
||||
last_name: Joi.string().required(),
|
||||
address_1: Joi.string().required(),
|
||||
address_2: Joi.string().allow(""),
|
||||
address_2: Joi.string().allow(null),
|
||||
city: Joi.string().required(),
|
||||
country_code: Joi.string().required(),
|
||||
province: Joi.string().allow(""),
|
||||
province: Joi.string().allow(null),
|
||||
postal_code: Joi.string().required(),
|
||||
phone: Joi.string().optional(),
|
||||
metadata: Joi.object(),
|
||||
metadata: Joi.object().allow(null),
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -941,28 +941,11 @@
|
||||
exec-sh "^0.3.2"
|
||||
minimist "^1.2.0"
|
||||
|
||||
"@hapi/address@^4.1.0":
|
||||
version "4.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@hapi/address/-/address-4.1.0.tgz#d60c5c0d930e77456fdcde2598e77302e2955e1d"
|
||||
integrity sha512-SkszZf13HVgGmChdHo/PxchnSaCJ6cetVqLzyciudzZRT0jcOouIF/Q93mgjw8cce+D+4F4C1Z/WrfFN+O3VHQ==
|
||||
dependencies:
|
||||
"@hapi/hoek" "^9.0.0"
|
||||
|
||||
"@hapi/formula@^2.0.0":
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@hapi/formula/-/formula-2.0.0.tgz#edade0619ed58c8e4f164f233cda70211e787128"
|
||||
integrity sha512-V87P8fv7PI0LH7LiVi8Lkf3x+KCO7pQozXRssAHNXXL9L1K+uyu4XypLXwxqVDKgyQai6qj3/KteNlrqDx4W5A==
|
||||
|
||||
"@hapi/hoek@^9.0.0":
|
||||
version "9.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@hapi/hoek/-/hoek-9.1.0.tgz#6c9eafc78c1529248f8f4d92b0799a712b6052c6"
|
||||
integrity sha512-i9YbZPN3QgfighY/1X1Pu118VUz2Fmmhd6b2n0/O8YVgGGfw0FbUYoA97k7FkpGJ+pLCFEDLUmAPPV4D1kpeFw==
|
||||
|
||||
"@hapi/pinpoint@^2.0.0":
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@hapi/pinpoint/-/pinpoint-2.0.0.tgz#805b40d4dbec04fc116a73089494e00f073de8df"
|
||||
integrity sha512-vzXR5MY7n4XeIvLpfl3HtE3coZYO4raKXW766R6DZw/6aLqR26iuZ109K7a0NtF2Db0jxqh7xz2AxkUwpUFybw==
|
||||
|
||||
"@hapi/topo@^5.0.0":
|
||||
version "5.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@hapi/topo/-/topo-5.0.0.tgz#c19af8577fa393a06e9c77b60995af959be721e7"
|
||||
@@ -1153,6 +1136,23 @@
|
||||
"@types/yargs" "^15.0.0"
|
||||
chalk "^3.0.0"
|
||||
|
||||
"@sideway/address@^4.1.0":
|
||||
version "4.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@sideway/address/-/address-4.1.0.tgz#0b301ada10ac4e0e3fa525c90615e0b61a72b78d"
|
||||
integrity sha512-wAH/JYRXeIFQRsxerIuLjgUu2Xszam+O5xKeatJ4oudShOOirfmsQ1D6LL54XOU2tizpCYku+s1wmU0SYdpoSA==
|
||||
dependencies:
|
||||
"@hapi/hoek" "^9.0.0"
|
||||
|
||||
"@sideway/formula@^3.0.0":
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@sideway/formula/-/formula-3.0.0.tgz#fe158aee32e6bd5de85044be615bc08478a0a13c"
|
||||
integrity sha512-vHe7wZ4NOXVfkoRb8T5otiENVlT7a3IAiw7H5M2+GO+9CDgcVUUsX1zalAztCmwyOr2RUTGJdgB+ZvSVqmdHmg==
|
||||
|
||||
"@sideway/pinpoint@^2.0.0":
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@sideway/pinpoint/-/pinpoint-2.0.0.tgz#cff8ffadc372ad29fd3f78277aeb29e632cc70df"
|
||||
integrity sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==
|
||||
|
||||
"@sinonjs/commons@^1.7.0":
|
||||
version "1.7.2"
|
||||
resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.7.2.tgz#505f55c74e0272b43f6c52d81946bed7058fc0e2"
|
||||
@@ -3241,16 +3241,16 @@ joi-objectid@^3.0.1:
|
||||
resolved "https://registry.yarnpkg.com/joi-objectid/-/joi-objectid-3.0.1.tgz#63ace7860f8e1a993a28d40c40ffd8eff01a3668"
|
||||
integrity sha512-V/3hbTlGpvJ03Me6DJbdBI08hBTasFOmipsauOsxOSnsF1blxV537WTl1zPwbfcKle4AK0Ma4OPnzMH4LlvTpQ==
|
||||
|
||||
joi@^17.2.1:
|
||||
version "17.2.1"
|
||||
resolved "https://registry.yarnpkg.com/joi/-/joi-17.2.1.tgz#e5140fdf07e8fecf9bc977c2832d1bdb1e3f2a0a"
|
||||
integrity sha512-YT3/4Ln+5YRpacdmfEfrrKh50/kkgX3LgBltjqnlMPIYiZ4hxXZuVJcxmsvxsdeHg9soZfE3qXxHC2tMpCCBOA==
|
||||
joi@^17.3.0:
|
||||
version "17.3.0"
|
||||
resolved "https://registry.yarnpkg.com/joi/-/joi-17.3.0.tgz#f1be4a6ce29bc1716665819ac361dfa139fff5d2"
|
||||
integrity sha512-Qh5gdU6niuYbUIUV5ejbsMiiFmBdw8Kcp8Buj2JntszCkCfxJ9Cz76OtHxOZMPXrt5810iDIXs+n1nNVoquHgg==
|
||||
dependencies:
|
||||
"@hapi/address" "^4.1.0"
|
||||
"@hapi/formula" "^2.0.0"
|
||||
"@hapi/hoek" "^9.0.0"
|
||||
"@hapi/pinpoint" "^2.0.0"
|
||||
"@hapi/topo" "^5.0.0"
|
||||
"@sideway/address" "^4.1.0"
|
||||
"@sideway/formula" "^3.0.0"
|
||||
"@sideway/pinpoint" "^2.0.0"
|
||||
|
||||
"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0:
|
||||
version "4.0.0"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "medusa-file-spaces",
|
||||
"version": "1.0.13",
|
||||
"version": "1.0.12-alpha.176+0646bd3",
|
||||
"description": "Digital Ocean Spaces file connector for Medusa",
|
||||
"main": "index.js",
|
||||
"repository": {
|
||||
@@ -40,9 +40,9 @@
|
||||
"aws-sdk": "^2.710.0",
|
||||
"body-parser": "^1.19.0",
|
||||
"express": "^4.17.1",
|
||||
"medusa-core-utils": "^1.0.11",
|
||||
"medusa-test-utils": "^1.0.13",
|
||||
"medusa-core-utils": "^1.0.12-alpha.787+0646bd3",
|
||||
"medusa-test-utils": "^1.0.12-alpha.176+0646bd3",
|
||||
"stripe": "^8.50.0"
|
||||
},
|
||||
"gitHead": "3cc7cbe5124cbcbb75f6e1435db4dcfaa2a60408"
|
||||
"gitHead": "0646bd395a6056657cb0aa93c13699c4a9dbbcdd"
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "medusa-fulfillment-manual",
|
||||
"version": "1.0.12",
|
||||
"version": "1.0.12-alpha.208+0646bd3",
|
||||
"description": "A manual fulfillment provider for Medusa",
|
||||
"main": "index.js",
|
||||
"repository": {
|
||||
@@ -36,7 +36,7 @@
|
||||
"@babel/plugin-transform-instanceof": "^7.8.3",
|
||||
"@babel/runtime": "^7.7.6",
|
||||
"express": "^4.17.1",
|
||||
"medusa-core-utils": "^1.0.11"
|
||||
"medusa-core-utils": "^1.0.12-alpha.787+0646bd3"
|
||||
},
|
||||
"gitHead": "3cc7cbe5124cbcbb75f6e1435db4dcfaa2a60408"
|
||||
"gitHead": "0646bd395a6056657cb0aa93c13699c4a9dbbcdd"
|
||||
}
|
||||
@@ -36,6 +36,11 @@ class ManualFulfillmentService extends FulfillmentService {
|
||||
return Promise.resolve({})
|
||||
}
|
||||
|
||||
createFulfillment() {
|
||||
// No data is being sent anywhere
|
||||
return Promise.resolve({})
|
||||
}
|
||||
|
||||
cancelFulfillment() {
|
||||
return Promise.resolve({})
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "medusa-fulfillment-webshipper",
|
||||
"version": "1.0.11",
|
||||
"version": "1.0.10-alpha.166+0646bd3",
|
||||
"description": "Webshipper Fulfillment provider for Medusa",
|
||||
"main": "index.js",
|
||||
"repository": {
|
||||
@@ -36,7 +36,7 @@
|
||||
"body-parser": "^1.19.0",
|
||||
"cors": "^2.8.5",
|
||||
"express": "^4.17.1",
|
||||
"medusa-core-utils": "^1.0.11"
|
||||
"medusa-core-utils": "^1.0.12-alpha.787+0646bd3"
|
||||
},
|
||||
"gitHead": "3cc7cbe5124cbcbb75f6e1435db4dcfaa2a60408"
|
||||
"gitHead": "0646bd395a6056657cb0aa93c13699c4a9dbbcdd"
|
||||
}
|
||||
@@ -7,10 +7,18 @@ class WebshipperFulfillmentService extends FulfillmentService {
|
||||
constructor({ logger, swapService, orderService }, options) {
|
||||
super()
|
||||
|
||||
this.logger_ = logger
|
||||
this.orderService_ = orderService
|
||||
this.options_ = options
|
||||
|
||||
/** @private @const {logger} */
|
||||
this.logger_ = logger
|
||||
|
||||
/** @private @const {OrderService} */
|
||||
this.orderService_ = orderService
|
||||
|
||||
/** @private @const {SwapService} */
|
||||
this.swapService_ = swapService
|
||||
|
||||
/** @private @const {AxiosClient} */
|
||||
this.client_ = new Webshipper({
|
||||
account: this.options_.account,
|
||||
token: this.options_.api_token,
|
||||
@@ -38,15 +46,19 @@ class WebshipperFulfillmentService extends FulfillmentService {
|
||||
}))
|
||||
}
|
||||
|
||||
async validateFulfillmentData(data, _) {
|
||||
if (data.require_drop_point) {
|
||||
async validateFulfillmentData(optionData, data, _) {
|
||||
if (optionData.require_drop_point) {
|
||||
if (!data.drop_point_id) {
|
||||
throw new Error("Must have drop point id")
|
||||
} else {
|
||||
// TODO: validate that the drop point exists
|
||||
}
|
||||
}
|
||||
return data
|
||||
|
||||
return {
|
||||
...optionData,
|
||||
...data,
|
||||
}
|
||||
}
|
||||
|
||||
async validateOption(data) {
|
||||
@@ -69,7 +81,22 @@ class WebshipperFulfillmentService extends FulfillmentService {
|
||||
* Creates a return shipment in webshipper using the given method data, and
|
||||
* return lines.
|
||||
*/
|
||||
async createReturn(methodData, returnLines, fromOrder) {
|
||||
async createReturn(returnOrder) {
|
||||
let fromOrder
|
||||
if (returnOrder.order_id) {
|
||||
fromOrder = await this.orderService_.retrieve(returnOrder.order_id, {
|
||||
select: ["total"],
|
||||
relations: ["shipping_address", "returns"],
|
||||
})
|
||||
} else if (returnOrder.swap) {
|
||||
fromOrder = await this.orderService_.retrieve(returnOrder.swap.order_id, {
|
||||
select: ["total"],
|
||||
relations: ["shipping_address", "returns"],
|
||||
})
|
||||
}
|
||||
|
||||
const methodData = returnOrder.shipping_method.data
|
||||
|
||||
const relationships = {
|
||||
shipping_rate: {
|
||||
data: {
|
||||
@@ -79,7 +106,8 @@ class WebshipperFulfillmentService extends FulfillmentService {
|
||||
},
|
||||
}
|
||||
|
||||
const existing = fromOrder.metadata.webshipper_order_id
|
||||
const existing =
|
||||
fromOrder.metadata && fromOrder.metadata.webshipper_order_id
|
||||
if (existing) {
|
||||
relationships.order = {
|
||||
data: {
|
||||
@@ -93,8 +121,9 @@ class WebshipperFulfillmentService extends FulfillmentService {
|
||||
if (this.invoiceGenerator_) {
|
||||
const base64Invoice = await this.invoiceGenerator_.createReturnInvoice(
|
||||
fromOrder,
|
||||
returnLines
|
||||
returnOrder.items
|
||||
)
|
||||
|
||||
docs.push({
|
||||
document_size: "A4",
|
||||
document_format: "PDF",
|
||||
@@ -108,7 +137,7 @@ class WebshipperFulfillmentService extends FulfillmentService {
|
||||
type: "shipments",
|
||||
attributes: {
|
||||
reference: `R${fromOrder.display_id}-${fromOrder.returns.length + 1}`,
|
||||
ext_ref: `${fromOrder._id}.${fromOrder.returns.length}`,
|
||||
ext_ref: `${fromOrder.id}.${returnOrder.id}`,
|
||||
is_return: true,
|
||||
included_documents: docs,
|
||||
packages: [
|
||||
@@ -121,21 +150,20 @@ class WebshipperFulfillmentService extends FulfillmentService {
|
||||
width: 15,
|
||||
length: 15,
|
||||
},
|
||||
customs_lines: returnLines.map((item) => {
|
||||
customs_lines: returnOrder.items.map(({ item, quantity }) => {
|
||||
return {
|
||||
ext_ref: item._id,
|
||||
sku: item.content.variant.sku,
|
||||
ext_ref: item.id,
|
||||
sku: item.variant.sku,
|
||||
description: item.title,
|
||||
quantity: item.quantity,
|
||||
quantity: quantity,
|
||||
country_of_origin:
|
||||
item.content.variant.metadata &&
|
||||
item.content.variant.metadata.origin_country,
|
||||
item.variant.origin_country ||
|
||||
item.variant.product.origin_country,
|
||||
tarif_number:
|
||||
item.content.variant.metadata &&
|
||||
item.content.variant.metadata.hs_code,
|
||||
unit_price: item.content.unit_price,
|
||||
vat_percent: fromOrder.tax_rate * 100,
|
||||
currency: fromOrder.currency_code,
|
||||
item.variant.hs_code || item.variant.product.hs_code,
|
||||
unit_price: item.unit_price / 100,
|
||||
vat_percent: fromOrder.tax_rate,
|
||||
currency: fromOrder.currency_code.toUpperCase(),
|
||||
}
|
||||
}),
|
||||
},
|
||||
@@ -148,7 +176,7 @@ class WebshipperFulfillmentService extends FulfillmentService {
|
||||
address_2: shipping_address.address_2,
|
||||
zip: shipping_address.postal_code,
|
||||
city: shipping_address.city,
|
||||
country_code: shipping_address.country_code,
|
||||
country_code: shipping_address.country_code.toUpperCase(),
|
||||
state: shipping_address.province,
|
||||
phone: shipping_address.phone,
|
||||
email: fromOrder.email,
|
||||
@@ -196,7 +224,12 @@ class WebshipperFulfillmentService extends FulfillmentService {
|
||||
return toReturn
|
||||
}
|
||||
|
||||
async createOrder(methodData, fulfillmentItems, fromOrder) {
|
||||
async createFulfillment(
|
||||
methodData,
|
||||
fulfillmentItems,
|
||||
fromOrder,
|
||||
fulfillment
|
||||
) {
|
||||
const existing =
|
||||
fromOrder.metadata && fromOrder.metadata.webshipper_order_id
|
||||
|
||||
@@ -228,13 +261,12 @@ class WebshipperFulfillmentService extends FulfillmentService {
|
||||
})
|
||||
}
|
||||
|
||||
let visible_ref = `${fromOrder.display_id}-${
|
||||
fromOrder.fulfillments.length + 1
|
||||
}`
|
||||
let ext_ref = `${fromOrder._id}.${fromOrder.fulfillments.length}`
|
||||
let id = fulfillment.id
|
||||
let visible_ref = `${fromOrder.display_id}-${id.substr(id.length - 4)}`
|
||||
let ext_ref = `${fromOrder.id}.${fulfillment.id}`
|
||||
|
||||
if (fromOrder.is_swap) {
|
||||
ext_ref = `S${fromOrder._id}.${fromOrder.fulfillments.length}`
|
||||
ext_ref = `${fromOrder.id}.${fulfillment.id}`
|
||||
visible_ref = `S-${fromOrder.display_id}`
|
||||
}
|
||||
|
||||
@@ -247,17 +279,17 @@ class WebshipperFulfillmentService extends FulfillmentService {
|
||||
visible_ref,
|
||||
order_lines: fulfillmentItems.map((item) => {
|
||||
return {
|
||||
ext_ref: item._id,
|
||||
sku: item.content.variant.sku,
|
||||
ext_ref: item.id,
|
||||
sku: item.variant.sku,
|
||||
description: item.title,
|
||||
quantity: item.quantity,
|
||||
country_of_origin:
|
||||
item.content.variant.metadata &&
|
||||
item.content.variant.metadata.origin_country,
|
||||
item.variant.origin_country ||
|
||||
item.variant.product.origin_country,
|
||||
tarif_number:
|
||||
item.content.variant.metadata &&
|
||||
item.content.variant.metadata.hs_code,
|
||||
unit_price: item.content.unit_price,
|
||||
item.variant.hs_code || item.variant.product.hs_code,
|
||||
unit_price: item.unit_price / 100,
|
||||
vat_percent: fromOrder.tax_rate,
|
||||
}
|
||||
}),
|
||||
delivery_address: {
|
||||
@@ -266,12 +298,12 @@ class WebshipperFulfillmentService extends FulfillmentService {
|
||||
address_2: shipping_address.address_2,
|
||||
zip: shipping_address.postal_code,
|
||||
city: shipping_address.city,
|
||||
country_code: shipping_address.country_code,
|
||||
country_code: shipping_address.country_code.toUpperCase(),
|
||||
state: shipping_address.province,
|
||||
phone: shipping_address.phone,
|
||||
email: fromOrder.email,
|
||||
},
|
||||
currency: fromOrder.currency_code,
|
||||
currency: fromOrder.currency_code.toUpperCase(),
|
||||
},
|
||||
relationships: {
|
||||
order_channel: {
|
||||
@@ -296,7 +328,7 @@ class WebshipperFulfillmentService extends FulfillmentService {
|
||||
zip: methodData.drop_point_zip,
|
||||
address_1: methodData.drop_point_address_1,
|
||||
city: methodData.drop_point_city,
|
||||
country_code: methodData.drop_point_country_code,
|
||||
country_code: methodData.drop_point_country_code.toUpperCase(),
|
||||
}
|
||||
}
|
||||
if (invoice) {
|
||||
@@ -326,7 +358,7 @@ class WebshipperFulfillmentService extends FulfillmentService {
|
||||
const wsOrder = await this.retrieveRelationship(
|
||||
body.data.relationships.order
|
||||
)
|
||||
if (wsOrder.data.attributes.ext_ref) {
|
||||
if (wsOrder.data && wsOrder.data.attributes.ext_ref) {
|
||||
const trackingNumbers = body.data.attributes.tracking_links.map(
|
||||
(l) => l.number
|
||||
)
|
||||
@@ -334,23 +366,44 @@ class WebshipperFulfillmentService extends FulfillmentService {
|
||||
"."
|
||||
)
|
||||
|
||||
if (orderId.charAt(0) === "S") {
|
||||
const swap = await this.swapService_.retrieve(orderId.substring(1))
|
||||
const fulfillment = swap.fulfillments[fulfillmentIndex]
|
||||
await this.swapService_.createShipment(
|
||||
swap._id,
|
||||
fulfillment._id,
|
||||
trackingNumbers
|
||||
)
|
||||
} else {
|
||||
const order = await this.orderService_.retrieve(orderId)
|
||||
const fulfillment = order.fulfillments[fulfillmentIndex]
|
||||
if (fulfillment) {
|
||||
await this.orderService_.createShipment(
|
||||
order._id,
|
||||
fulfillment._id,
|
||||
if (orderId.charAt(0).toLowerCase() === "s") {
|
||||
if (fulfillmentIndex.startsWith("ful")) {
|
||||
return this.swapService_.createShipment(
|
||||
orderId,
|
||||
fulfillmentIndex,
|
||||
trackingNumbers
|
||||
)
|
||||
} else {
|
||||
const swap = await this.swapService_.retrieve(orderId.substring(1), {
|
||||
relations: ["fulfillments"],
|
||||
})
|
||||
const fulfillment = swap.fulfillments[fulfillmentIndex]
|
||||
return this.swapService_.createShipment(
|
||||
swap.id,
|
||||
fulfillment.id,
|
||||
trackingNumbers
|
||||
)
|
||||
}
|
||||
} else {
|
||||
if (fulfillmentIndex.startsWith("ful")) {
|
||||
return this.orderService_.createShipment(
|
||||
orderId,
|
||||
fulfillmentIndex,
|
||||
trackingNumbers
|
||||
)
|
||||
} else {
|
||||
const order = await this.orderService_.retrieve(orderId, {
|
||||
relations: ["fulfillments"],
|
||||
})
|
||||
|
||||
const fulfillment = order.fulfillments[fulfillmentIndex]
|
||||
if (fulfillment) {
|
||||
return this.orderService_.createShipment(
|
||||
order.id,
|
||||
fulfillment.id,
|
||||
trackingNumbers
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -392,7 +445,7 @@ class WebshipperFulfillmentService extends FulfillmentService {
|
||||
shipping_rate_id: id,
|
||||
delivery_address: {
|
||||
zip,
|
||||
country_code: countryCode,
|
||||
country_code: countryCode.toUpperCase(),
|
||||
address_1: address1,
|
||||
},
|
||||
},
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "medusa-interfaces",
|
||||
"version": "1.0.14",
|
||||
"version": "1.0.14-alpha.170+0646bd3",
|
||||
"description": "Core interfaces for Medusa",
|
||||
"main": "dist/index.js",
|
||||
"repository": {
|
||||
@@ -28,13 +28,14 @@
|
||||
"cross-env": "^5.2.1",
|
||||
"eslint": "^6.8.0",
|
||||
"jest": "^25.5.2",
|
||||
"prettier": "^1.19.1"
|
||||
"prettier": "^1.19.1",
|
||||
"typeorm": "^0.2.29"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"mongoose": "5.x"
|
||||
"typeorm": "0.x"
|
||||
},
|
||||
"dependencies": {
|
||||
"medusa-core-utils": "^1.0.11"
|
||||
"medusa-core-utils": "^1.0.12-alpha.787+0646bd3"
|
||||
},
|
||||
"gitHead": "3cc7cbe5124cbcbb75f6e1435db4dcfaa2a60408"
|
||||
"gitHead": "0646bd395a6056657cb0aa93c13699c4a9dbbcdd"
|
||||
}
|
||||
@@ -1,6 +1,39 @@
|
||||
import BaseService from "../base-service"
|
||||
import { In, Not } from "typeorm"
|
||||
|
||||
describe("BaseService", () => {
|
||||
describe("buildQuery_", () => {
|
||||
const baseService = new BaseService()
|
||||
|
||||
it("successfully creates query", () => {
|
||||
const q = baseService.buildQuery_(
|
||||
{
|
||||
id: "1234",
|
||||
test1: ["123", "12", "1"],
|
||||
test2: Not("this"),
|
||||
rec: {
|
||||
first: ["1", "2", "3"],
|
||||
},
|
||||
},
|
||||
{
|
||||
relations: ["1234"],
|
||||
}
|
||||
)
|
||||
|
||||
expect(q).toEqual({
|
||||
where: {
|
||||
id: "1234",
|
||||
test1: In(["123", "12", "1"]),
|
||||
test2: Not("this"),
|
||||
rec: {
|
||||
first: In(["1", "2", "3"]),
|
||||
},
|
||||
},
|
||||
relations: ["1234"],
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe("addDecorator", () => {
|
||||
const baseService = new BaseService()
|
||||
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
import { MedusaError } from "medusa-core-utils"
|
||||
import { In, FindOperator, getManager } from "typeorm"
|
||||
|
||||
/**
|
||||
* Common functionality for Services
|
||||
* @interface
|
||||
@@ -7,6 +10,179 @@ class BaseService {
|
||||
this.decorators_ = []
|
||||
}
|
||||
|
||||
withTransaction() {
|
||||
console.log("WARN: withTransaction called without custom implementation")
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to build TypeORM queries.
|
||||
*/
|
||||
buildQuery_(selector, config = {}) {
|
||||
const build = obj => {
|
||||
const where = Object.entries(obj).reduce((acc, [key, value]) => {
|
||||
switch (true) {
|
||||
case value instanceof FindOperator:
|
||||
acc[key] = value
|
||||
break
|
||||
case Array.isArray(value):
|
||||
acc[key] = In([...value])
|
||||
break
|
||||
case value !== null && typeof value === "object":
|
||||
acc[key] = build(value)
|
||||
break
|
||||
default:
|
||||
acc[key] = value
|
||||
break
|
||||
}
|
||||
|
||||
return acc
|
||||
}, {})
|
||||
|
||||
return where
|
||||
}
|
||||
|
||||
const query = {
|
||||
where: build(selector),
|
||||
}
|
||||
|
||||
if ("skip" in config) {
|
||||
query.skip = config.skip
|
||||
}
|
||||
|
||||
if ("take" in config) {
|
||||
query.take = config.take
|
||||
}
|
||||
|
||||
if ("relations" in config) {
|
||||
query.relations = config.relations
|
||||
}
|
||||
|
||||
if ("select" in config) {
|
||||
query.select = config.select
|
||||
}
|
||||
|
||||
if ("order" in config) {
|
||||
query.order = config.order
|
||||
}
|
||||
|
||||
return query
|
||||
}
|
||||
|
||||
/**
|
||||
* Confirms whether a given raw id is valid. Fails if the provided
|
||||
* id is null or undefined. The validate function takes an optional config
|
||||
* param, to support checking id prefix and length.
|
||||
* @param {string} rawId - the id to validate.
|
||||
* @param {object?} config - optional config
|
||||
* @returns {string} the rawId given that nothing failed
|
||||
*/
|
||||
validateId_(rawId, config = {}) {
|
||||
const { prefix, length } = config
|
||||
if (!rawId) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
`Failed to validate id: ${rawId}`
|
||||
)
|
||||
}
|
||||
|
||||
if (prefix || length) {
|
||||
const [pre, rand] = rawId.split("_")
|
||||
if (prefix && pre !== prefix) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
`The provided id: ${rawId} does not adhere to prefix constraint: ${prefix}`
|
||||
)
|
||||
}
|
||||
|
||||
if (length && length !== rand.length) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
`The provided id: ${rawId} does not adhere to length constraint: ${length}`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return rawId
|
||||
}
|
||||
|
||||
shouldRetryTransaction(err) {
|
||||
const code = typeof err === "object" ? String(err.code) : null
|
||||
return code === "40001" || code === "40P01"
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps some work within a transactional block. If the service already has
|
||||
* a transaction manager attached this will be reused, otherwise a new
|
||||
* transaction manager is created.
|
||||
* @param {function} work - the transactional work to be done
|
||||
* @param {string} isolation - the isolation level to be used for the work.
|
||||
* @return {any} the result of the transactional work
|
||||
*/
|
||||
async atomicPhase_(work, isolation) {
|
||||
if (this.transactionManager_) {
|
||||
return work(this.transactionManager_)
|
||||
} else {
|
||||
const temp = this.manager_
|
||||
const doWork = async m => {
|
||||
this.manager_ = m
|
||||
this.transactionManager_ = m
|
||||
try {
|
||||
const result = await work(m)
|
||||
this.manager_ = temp
|
||||
this.transactionManager_ = undefined
|
||||
return result
|
||||
} catch (error) {
|
||||
this.manager_ = temp
|
||||
this.transactionManager_ = undefined
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
if (isolation) {
|
||||
let result
|
||||
try {
|
||||
result = await this.manager_.transaction(isolation, m => doWork(m))
|
||||
return result
|
||||
} catch (error) {
|
||||
if (this.shouldRetryTransaction(error)) {
|
||||
return this.manager_.transaction(isolation, m => doWork(m))
|
||||
} else {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
}
|
||||
return this.manager_.transaction(m => doWork(m))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Dedicated method to set metadata.
|
||||
* @param {string} obj - the entity to apply metadata to.
|
||||
* @param {object} metadata - the metadata to set
|
||||
* @return {Promise} resolves to the updated result.
|
||||
*/
|
||||
setMetadata_(obj, metadata) {
|
||||
const existing = obj.metadata || {}
|
||||
const newData = {}
|
||||
for (const [key, value] of Object.entries(metadata)) {
|
||||
if (typeof key !== "string") {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_ARGUMENT,
|
||||
"Key type is invalid. Metadata keys must be strings"
|
||||
)
|
||||
}
|
||||
newData[key] = value
|
||||
}
|
||||
|
||||
const updated = {
|
||||
...existing,
|
||||
...newData,
|
||||
}
|
||||
|
||||
return updated
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a decorator to a service. The decorator must be a function and should
|
||||
* return a decorated object.
|
||||
|
||||
@@ -60,7 +60,7 @@ class BaseFulfillmentService extends BaseService {
|
||||
throw Error("calculatePrice must be overridden by the child class")
|
||||
}
|
||||
|
||||
createOrder() {
|
||||
createFulfillment() {
|
||||
throw Error("createOrder must be overridden by the child class")
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "medusa-payment-adyen",
|
||||
"version": "1.0.13",
|
||||
"version": "1.0.12-alpha.176+0646bd3",
|
||||
"description": "Adyen Payment provider for Medusa Commerce",
|
||||
"main": "index.js",
|
||||
"repository": {
|
||||
@@ -24,7 +24,7 @@
|
||||
"cross-env": "^7.0.2",
|
||||
"eslint": "^6.8.0",
|
||||
"jest": "^25.5.2",
|
||||
"medusa-test-utils": "^1.0.13"
|
||||
"medusa-test-utils": "^1.0.12-alpha.176+0646bd3"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "babel src -d .",
|
||||
@@ -42,7 +42,7 @@
|
||||
"body-parser": "^1.19.0",
|
||||
"cors": "^2.8.5",
|
||||
"express": "^4.17.1",
|
||||
"medusa-core-utils": "^1.0.11"
|
||||
"medusa-core-utils": "^1.0.12-alpha.787+0646bd3"
|
||||
},
|
||||
"gitHead": "3cc7cbe5124cbcbb75f6e1435db4dcfaa2a60408"
|
||||
"gitHead": "0646bd395a6056657cb0aa93c13699c4a9dbbcdd"
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
export default async (req, res) => {
|
||||
const adyenService = req.scope.resolve("adyenService")
|
||||
const eventBus = req.scope.resolve("eventBusService")
|
||||
|
||||
const notification = req.body
|
||||
const event = notification.notificationItems[0].NotificationRequestItem
|
||||
|
||||
const valid = adyenService.validateNotification(event)
|
||||
|
||||
if (!valid) {
|
||||
res.status(401).send(`Unauthorized webhook event`)
|
||||
return
|
||||
}
|
||||
|
||||
eventBus.emit("adyen.notification_received", event)
|
||||
res.status(200).send("[accepted]")
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
export default async (req, res) => {
|
||||
const adyenService = req.scope.resolve("adyenService")
|
||||
|
||||
const notification = req.body
|
||||
const event = notification.notificationItems[0].NotificationRequestItem
|
||||
|
||||
const valid = adyenService.validateNotification(event)
|
||||
|
||||
if (!valid) {
|
||||
res.status(401).send(`Unauthorized webhook event`)
|
||||
return
|
||||
}
|
||||
|
||||
if (event.success === "true" && event.eventCode === "AUTHORISATION") {
|
||||
const orderService = req.scope.resolve("orderService")
|
||||
const cartService = req.scope.resolve("cartService")
|
||||
|
||||
const cartId = event.additionalData["metadata.cart_id"]
|
||||
|
||||
try {
|
||||
const order = await orderService.retrieveByCartId(cartId)
|
||||
console.log(order)
|
||||
} catch (error) {
|
||||
const cart = await cartService.retrieve(cartId)
|
||||
await orderService.createFromCart(cart)
|
||||
}
|
||||
|
||||
// Create from cart
|
||||
res.status(200).send("[accepted]")
|
||||
return
|
||||
}
|
||||
|
||||
if (event.success === "true" && event.eventCode === "CAPTURE") {
|
||||
// Create from cart
|
||||
console.log("Captured")
|
||||
res.status(200).send("[accepted]")
|
||||
return
|
||||
}
|
||||
|
||||
res.status(200).send("[accepted]")
|
||||
}
|
||||
@@ -18,12 +18,12 @@ export default (app, rootDirectory) => {
|
||||
})
|
||||
)
|
||||
|
||||
app.use("/adyen-hooks", route)
|
||||
app.use("/adyen/webhooks", route)
|
||||
|
||||
route.post(
|
||||
"/capture",
|
||||
"/notification",
|
||||
bodyParser.json(),
|
||||
middlewares.wrap(require("./capture-hook").default)
|
||||
middlewares.wrap(require("./adyen-notification").default)
|
||||
)
|
||||
|
||||
return app
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
import { Validator, MedusaError } from "medusa-core-utils"
|
||||
|
||||
export default async (req, res) => {
|
||||
const schema = Validator.object().keys({
|
||||
cart_id: Validator.string().required(),
|
||||
provider_id: Validator.string().required(),
|
||||
payment_data: Validator.object().required(),
|
||||
})
|
||||
|
||||
const { value, error } = schema.validate(req.body)
|
||||
if (error) {
|
||||
throw new MedusaError(MedusaError.Types.INVALID_DATA, error.details)
|
||||
}
|
||||
|
||||
try {
|
||||
const cartService = req.scope.resolve("cartService")
|
||||
const paymentProvider = req.scope.resolve(`pp_${value.provider_id}`)
|
||||
|
||||
const cart = await cartService.retrieve(value.cart_id)
|
||||
|
||||
const { data } = await paymentProvider.authorizePayment(
|
||||
cart,
|
||||
value.payment_data.paymentMethod
|
||||
)
|
||||
|
||||
const transactionReference = data.pspReference
|
||||
|
||||
let newPaymentSession = cart.payment_sessions.find(
|
||||
(ps) => ps.provider_id === value.provider_id
|
||||
)
|
||||
|
||||
newPaymentSession = {
|
||||
...newPaymentSession,
|
||||
data,
|
||||
}
|
||||
|
||||
await cartService.setMetadata(
|
||||
cart._id,
|
||||
"adyen_transaction_ref",
|
||||
transactionReference
|
||||
)
|
||||
|
||||
await cartService.updatePaymentSession(
|
||||
cart._id,
|
||||
value.provider_id,
|
||||
newPaymentSession
|
||||
)
|
||||
|
||||
await cartService.setPaymentMethod(cart._id, {
|
||||
provider_id: value.provider_id,
|
||||
data,
|
||||
})
|
||||
|
||||
res.status(200).json({ data })
|
||||
} catch (err) {
|
||||
throw err
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
import { Validator, MedusaError } from "medusa-core-utils"
|
||||
|
||||
export default async (req, res) => {
|
||||
const schema = Validator.object().keys({
|
||||
payload: Validator.string().required(),
|
||||
payment_data: Validator.string().required(),
|
||||
provider_id: Validator.string().required(),
|
||||
})
|
||||
|
||||
const { value, error } = schema.validate(req.body)
|
||||
if (error) {
|
||||
throw new MedusaError(MedusaError.Types.INVALID_DATA, error.details)
|
||||
}
|
||||
|
||||
try {
|
||||
const adyen = req.scope.resolve("adyenService")
|
||||
const paymentProvider = req.scope.resolve(`pp_${value.provider_id}`)
|
||||
|
||||
const { data } = await adyen.checkPaymentResult(
|
||||
value.payment_data,
|
||||
value.payload
|
||||
)
|
||||
|
||||
const status = await paymentProvider.getStatus(data)
|
||||
|
||||
res.status(200).json({ status })
|
||||
} catch (err) {
|
||||
throw err
|
||||
}
|
||||
}
|
||||
@@ -26,29 +26,5 @@ export default (app, rootDirectory) => {
|
||||
middlewares.wrap(require("./retrieve-payment-methods").default)
|
||||
)
|
||||
|
||||
route.post(
|
||||
"/authorize",
|
||||
bodyParser.json(),
|
||||
middlewares.wrap(require("./authorize-payment").default)
|
||||
)
|
||||
|
||||
route.post(
|
||||
"/update",
|
||||
bodyParser.json(),
|
||||
middlewares.wrap(require("./update-payment").default)
|
||||
)
|
||||
|
||||
route.post(
|
||||
"/payment-status",
|
||||
bodyParser.json(),
|
||||
middlewares.wrap(require("./check-payment-status").default)
|
||||
)
|
||||
|
||||
route.get(
|
||||
"/payment-status",
|
||||
bodyParser.json(),
|
||||
middlewares.wrap(require("./check-payment-status").default)
|
||||
)
|
||||
|
||||
return app
|
||||
}
|
||||
@@ -13,39 +13,44 @@ export default async (req, res) => {
|
||||
try {
|
||||
const adyenService = req.scope.resolve("adyenService")
|
||||
const cartService = req.scope.resolve("cartService")
|
||||
const regionService = req.scope.resolve("regionService")
|
||||
const totalsService = req.scope.resolve("totalsService")
|
||||
|
||||
const cart = await cartService.retrieve(value.cart_id)
|
||||
const region = await regionService.retrieve(cart.region_id)
|
||||
const total = await totalsService.getTotal(cart)
|
||||
const cart = await cartService.retrieve(value.cart_id, {
|
||||
select: ["total"],
|
||||
relations: ["region", "region.payment_providers", "payment_sessions"],
|
||||
})
|
||||
|
||||
const allowedMethods = cart.payment_sessions.map(
|
||||
(ps) => ps.provider_id.split("Adyen")[0]
|
||||
)
|
||||
const allowedMethods = cart.payment_sessions.map((ps) => {
|
||||
if (ps.provider_id.includes("adyen")) {
|
||||
return ps.provider_id.split("-adyen")[0]
|
||||
}
|
||||
})
|
||||
|
||||
if (allowedMethods.length === 0) {
|
||||
res.status(200).json({ paymentMethods: {} })
|
||||
return
|
||||
}
|
||||
|
||||
const { data } = await adyenService.retrievePaymentMethods(
|
||||
cart,
|
||||
const pmMethods = await adyenService.retrievePaymentMethods(
|
||||
allowedMethods,
|
||||
total,
|
||||
region.currency_code
|
||||
cart.total,
|
||||
cart.currency_code,
|
||||
cart.customer_id || ""
|
||||
)
|
||||
|
||||
// Adyen does not behave 100% correctly in regards to allowed methods
|
||||
// Therefore, we sanity filter before sending them to the storefront
|
||||
const { paymentMethods, groups } = data
|
||||
const { paymentMethods, groups, storedPaymentMethods } = pmMethods
|
||||
const methods = paymentMethods.filter((pm) =>
|
||||
allowedMethods.includes(pm.type)
|
||||
)
|
||||
|
||||
res
|
||||
.status(200)
|
||||
.json({ paymentMethods: { paymentMethods: methods, groups } })
|
||||
const response = {
|
||||
paymentMethods: methods,
|
||||
groups,
|
||||
storedPaymentMethods,
|
||||
}
|
||||
|
||||
res.status(200).json({ payment_methods: response })
|
||||
} catch (err) {
|
||||
throw err
|
||||
}
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
import { Validator, MedusaError } from "medusa-core-utils"
|
||||
|
||||
export default async (req, res) => {
|
||||
const schema = Validator.object().keys({
|
||||
cart_id: Validator.string().required(),
|
||||
provider_id: Validator.string().required(),
|
||||
payment_data: Validator.object().required(),
|
||||
})
|
||||
|
||||
const { value, error } = schema.validate(req.body)
|
||||
if (error) {
|
||||
throw new MedusaError(MedusaError.Types.INVALID_DATA, error.details)
|
||||
}
|
||||
|
||||
try {
|
||||
const cartService = req.scope.resolve("cartService")
|
||||
const paymentProvider = req.scope.resolve(`pp_${value.provider_id}`)
|
||||
|
||||
const cart = await cartService.retrieve(value.cart_id)
|
||||
|
||||
const { data } = await paymentProvider.updatePayment(
|
||||
value.payment_data.paymentData,
|
||||
value.payment_data.details
|
||||
)
|
||||
|
||||
await cartService.updatePaymentSession(cart._id, value.provider_id, data)
|
||||
|
||||
res.status(200).json({ data })
|
||||
} catch (err) {
|
||||
throw err
|
||||
}
|
||||
}
|
||||
@@ -2,72 +2,141 @@ import axios from "axios"
|
||||
import _ from "lodash"
|
||||
import { hmacValidator } from "@adyen/api-library"
|
||||
import { BaseService } from "medusa-interfaces"
|
||||
import { Client, Config, CheckoutAPI } from "@adyen/api-library"
|
||||
|
||||
class AdyenService extends BaseService {
|
||||
constructor({ regionService, cartService, totalsService }, options) {
|
||||
constructor({ cartService }, options) {
|
||||
super()
|
||||
|
||||
this.regionService_ = regionService
|
||||
|
||||
/** @private @constant {CartService} */
|
||||
this.cartService_ = cartService
|
||||
|
||||
this.totalsService_ = totalsService
|
||||
|
||||
/**
|
||||
* {
|
||||
* api_key: "",
|
||||
* notification_hmac: "",
|
||||
* return_url: "",
|
||||
* merchant_account: "",
|
||||
* origin: "",
|
||||
* environment: "",
|
||||
* live_endpoint_prefix: "",
|
||||
* payment_endpoint: ""
|
||||
* }
|
||||
*/
|
||||
this.options_ = options
|
||||
|
||||
this.adyenCheckoutApi = axios.create({
|
||||
baseURL: "https://checkout-test.adyen.com/v53",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"x-API-key": this.options_.api_key,
|
||||
},
|
||||
/** @private @constant {AxiosClient} */
|
||||
this.adyenClient_ = this.initAdyenClient()
|
||||
|
||||
/** @private @constant {AdyenClient} */
|
||||
this.adyenPaymentApi = this.initPaymentClient()
|
||||
}
|
||||
|
||||
withTransaction(transactionManager) {
|
||||
if (!transactionManager) {
|
||||
return this
|
||||
}
|
||||
|
||||
const cloned = new AdyenService({
|
||||
cartService: this.cartService_,
|
||||
totalsService: this.totalsService_,
|
||||
})
|
||||
|
||||
this.adyenPaymentApi = axios.create({
|
||||
baseURL: "https://pal-test.adyen.com/pal/servlet/Payment/v52",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"x-API-key": this.options_.api_key,
|
||||
},
|
||||
})
|
||||
this.transactionManager_ = transactionManager
|
||||
|
||||
return cloned
|
||||
}
|
||||
|
||||
getOptions() {
|
||||
return this.options_
|
||||
}
|
||||
|
||||
validateNotification(event) {
|
||||
initPaymentClient() {
|
||||
return axios.create({
|
||||
baseURL: this.options_.payment_endpoint,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"x-API-key": this.options_.api_key,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
initAdyenClient() {
|
||||
const config = new Config()
|
||||
config.apiKey = this.options_.api_key
|
||||
config.merchantAccount = this.options_.merchant_account
|
||||
|
||||
const client = new Client({
|
||||
config,
|
||||
})
|
||||
|
||||
client.setEnvironment(
|
||||
this.options_.environment,
|
||||
this.options_.live_endpoint_prefix
|
||||
)
|
||||
|
||||
return client
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates an Adyen webhook notification
|
||||
* @param {object} notification - notification to validate
|
||||
* @returns {string} the status of the payment
|
||||
*/
|
||||
validateNotification(notification) {
|
||||
const validator = new hmacValidator()
|
||||
|
||||
const validated = validator.validateHMAC(
|
||||
event,
|
||||
notification,
|
||||
this.options_.notification_hmac
|
||||
)
|
||||
return validated
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve payment methods from Ayden using country as filter.
|
||||
* @param {string} countryCode - country code of cart
|
||||
* @param {string} shopperLocale - locale used on website
|
||||
* @returns {string} the status of the payment
|
||||
* Retrieve stored payment methods from Adyen.
|
||||
* @param {Customer} customer - customer to retrieve methods for
|
||||
* @returns {Promise} result containing the stored payment methods from Adyen
|
||||
*/
|
||||
async retrievePaymentMethods(cart, allowedMethods, total, currency) {
|
||||
async retrieveSavedMethods(customer) {
|
||||
let request = {
|
||||
allowedPaymentMethods: allowedMethods,
|
||||
amount: {
|
||||
value: total * 100,
|
||||
currency: currency,
|
||||
},
|
||||
merchantAccount: this.options_.merchant_account,
|
||||
channel: this.options_.channel,
|
||||
}
|
||||
|
||||
if (cart.customer_id) {
|
||||
request.shopperReference = cart.customer_id
|
||||
channel: "Web",
|
||||
shopperReference: customer.id,
|
||||
}
|
||||
|
||||
try {
|
||||
return await this.adyenCheckoutApi.post("/paymentMethods", request)
|
||||
const checkout = new CheckoutAPI(this.adyenClient_)
|
||||
const methods = await checkout.paymentMethods(request)
|
||||
return methods.storedPaymentMethods || []
|
||||
} catch (error) {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve payment methods from Adyen.
|
||||
* @param {string[]} allowedMethods - the allowed methods based on region
|
||||
* @param {number} total - total amount to be paid with payment methods
|
||||
* @param {string} currency - currency code to use for the payment
|
||||
* @param {string} customerId - id of the customer paying
|
||||
* @returns {Promise} result containing the payment methods from Adyen
|
||||
*/
|
||||
async retrievePaymentMethods(allowedMethods, total, currency, customerId) {
|
||||
let request = {
|
||||
allowedPaymentMethods: allowedMethods,
|
||||
amount: {
|
||||
value: total,
|
||||
currency: currency,
|
||||
},
|
||||
merchantAccount: this.options_.merchant_account,
|
||||
channel: "Web",
|
||||
shopperReference: customerId,
|
||||
}
|
||||
|
||||
try {
|
||||
const checkout = new CheckoutAPI(this.adyenClient_)
|
||||
return checkout.paymentMethods(request)
|
||||
} catch (error) {
|
||||
throw error
|
||||
}
|
||||
@@ -75,89 +144,218 @@ class AdyenService extends BaseService {
|
||||
|
||||
/**
|
||||
* Status for Adyen payment.
|
||||
* @param {Object} paymentData - payment method data from cart
|
||||
* @param {object} paymentData - payment method data from cart
|
||||
* @returns {string} the status of the payment
|
||||
*/
|
||||
async getStatus(_) {
|
||||
let status = "initial"
|
||||
getStatus(paymentData) {
|
||||
const { resultCode } = paymentData
|
||||
let status = "pending"
|
||||
|
||||
if (resultCode === "Pending") {
|
||||
return status
|
||||
}
|
||||
|
||||
if (resultCode === "Refused") {
|
||||
return status
|
||||
}
|
||||
|
||||
if (resultCode === "Error") {
|
||||
status = "error"
|
||||
}
|
||||
|
||||
if (resultCode === "Authorised") {
|
||||
status = "authorized"
|
||||
}
|
||||
|
||||
if (resultCode === "Canceled") {
|
||||
status = "canceled"
|
||||
}
|
||||
|
||||
if (resultCode === "ChallengeShopper") {
|
||||
status = "requires_more"
|
||||
}
|
||||
|
||||
if (resultCode === "RedirectShopper") {
|
||||
status = "requires_more"
|
||||
}
|
||||
|
||||
if (resultCode === "IdentifyShopper") {
|
||||
status = "requires_more"
|
||||
}
|
||||
|
||||
return status
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates Adyen payment object.
|
||||
* @param {any} _ - placeholder object
|
||||
* @returns {Object} empty payment data
|
||||
* @param {Cart} cart - cart to initiate payment for
|
||||
* @returns {object} empty payment data
|
||||
*/
|
||||
async createPayment(_) {
|
||||
return {}
|
||||
}
|
||||
|
||||
async retrievePayment(data) {
|
||||
return data
|
||||
}
|
||||
|
||||
async updatePayment(paymentData, details) {
|
||||
const request = {
|
||||
paymentData,
|
||||
details,
|
||||
}
|
||||
return this.adyenCheckoutApi.post("/payments/details", request)
|
||||
async createPayment(cart) {
|
||||
return { cart_id: cart.id }
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and authorizes an Ayden payment
|
||||
* @returns {Object} payment data result
|
||||
* Retrieves Adyen payment. This is not supported by adyen, so we simply
|
||||
* return the current payment method data
|
||||
* @param {object} data - payment session
|
||||
* @returns {object} payment method data
|
||||
*/
|
||||
async authorizePayment(cart, paymentMethod) {
|
||||
const region = await this.regionService_.retrieve(cart.region_id)
|
||||
const total = await this.totalsService_.getTotal(cart)
|
||||
async getPaymentData(paymentSession) {
|
||||
return { ...paymentSession.data }
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves Adyen payment. This is not supported by adyen, so we simply
|
||||
* return the current payment method data
|
||||
* @param {object} sessionData - the data of the payment to retrieve
|
||||
* @returns {Promise<object>} Stripe payment intent
|
||||
*/
|
||||
async retrieve(sessionData) {
|
||||
return sessionData
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and authorizes an Adyen payment.
|
||||
* Requires cart_id in context for authorization.
|
||||
* Return status of authorization result.
|
||||
* @param {object} sessionData - payment session data
|
||||
* @param {object} context - properties relevant to current context
|
||||
* @returns {Promise<{ status: string, data: object }>} result with data and status
|
||||
*/
|
||||
async authorizePayment(session, context) {
|
||||
const sessionData = session.data
|
||||
|
||||
const status = this.getStatus(sessionData)
|
||||
|
||||
// If session data is present, we already called authorize once.
|
||||
// Therefore, this is most likely a call for getting additional details
|
||||
if (status === "requires_more") {
|
||||
const updated = await this.updatePaymentData(sessionData, {
|
||||
details: sessionData.details,
|
||||
paymentData: sessionData.paymentData,
|
||||
})
|
||||
|
||||
return { data: updated, status: this.getStatus(updated) }
|
||||
}
|
||||
|
||||
if (status === "authorized") {
|
||||
return { data: sessionData, status: "authorized" }
|
||||
}
|
||||
|
||||
const cart = await this.cartService_.retrieve(session.cart_id, {
|
||||
select: ["total"],
|
||||
relations: ["region", "shipping_address"],
|
||||
})
|
||||
|
||||
const amount = {
|
||||
currency: cart.region.currency_code.toUpperCase(),
|
||||
value: cart.total,
|
||||
}
|
||||
|
||||
let request = {
|
||||
amount: {
|
||||
currency: region.currency_code,
|
||||
value: total * 100,
|
||||
},
|
||||
amount,
|
||||
shopperIP: context.ip_address || "",
|
||||
shopperReference: cart.customer_id,
|
||||
paymentMethod,
|
||||
reference: cart._id,
|
||||
paymentMethod: sessionData.paymentData.paymentMethod,
|
||||
reference: cart.id,
|
||||
merchantAccount: this.options_.merchant_account,
|
||||
returnUrl: this.options_.return_url,
|
||||
origin: this.options_.origin,
|
||||
channel: "Web",
|
||||
redirectFromIssuerMethod: "GET",
|
||||
browserInfo: sessionData.browserInfo || {},
|
||||
billingAddress: {
|
||||
city: cart.shipping_address.city,
|
||||
country: cart.shipping_address.country_code,
|
||||
houseNumberOrName: cart.shipping_address.address_2 || "",
|
||||
postalCode: cart.shipping_address.postal_code,
|
||||
stateOrProvice: cart.shipping_address.province || "",
|
||||
street: cart.shipping_address.address_1,
|
||||
},
|
||||
metadata: {
|
||||
cart_id: cart._id,
|
||||
cart_id: cart.id,
|
||||
},
|
||||
}
|
||||
|
||||
if (paymentMethod.storedPaymentMethodId) {
|
||||
// If customer chose to save the payment method
|
||||
if (sessionData.storePaymentMethod) {
|
||||
request.storePaymentMethod = "true"
|
||||
request.shopperInteraction = "Ecommerce"
|
||||
request.recurringProcessingModel = "CardOnFile"
|
||||
}
|
||||
|
||||
return await this.adyenCheckoutApi.post("/payments", request)
|
||||
const checkout = new CheckoutAPI(this.adyenClient_)
|
||||
|
||||
try {
|
||||
const authorizedPayment = await checkout.payments(request, {
|
||||
idempotencyKey: context.idempotency_key || "",
|
||||
})
|
||||
|
||||
return {
|
||||
data: authorizedPayment,
|
||||
status: this.getStatus(authorizedPayment),
|
||||
}
|
||||
} catch (error) {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
async checkPaymentResult(paymentData, payload) {
|
||||
const request = {
|
||||
paymentData,
|
||||
details: {
|
||||
payload,
|
||||
},
|
||||
async updatePaymentData(sessionData, update) {
|
||||
if (_.isEmpty(update.details)) {
|
||||
return { ...sessionData, ...update }
|
||||
}
|
||||
return this.adyenCheckoutApi.post("/payments/details", request)
|
||||
|
||||
const checkout = new CheckoutAPI(this.adyenClient_)
|
||||
const updated = await checkout.paymentsDetails(update)
|
||||
|
||||
return updated
|
||||
}
|
||||
|
||||
/**
|
||||
* Captures an Ayden payment
|
||||
* @param {Object} data - payment data to capture
|
||||
* @returns {Object} payment data result of capture
|
||||
* Updates an Adyen payment.
|
||||
* @param {object} paymentData - payment data to update
|
||||
* @param {details} details - details to update
|
||||
* @returns {Promise} result of the update operation
|
||||
*/
|
||||
async capturePayment(data) {
|
||||
const { pspReference, amount } = data
|
||||
async updatePayment(paymentData, details) {
|
||||
return paymentData
|
||||
}
|
||||
|
||||
/**
|
||||
* Additional details
|
||||
* @param {object} paymentData - payment data
|
||||
* @param {object} details - payment details
|
||||
* @returns {Promise} current payment result
|
||||
*/
|
||||
async additionalDetails(paymentData, details) {
|
||||
const request = {
|
||||
paymentData,
|
||||
details,
|
||||
}
|
||||
|
||||
const checkout = new CheckoutAPI(this.adyenClient_)
|
||||
return checkout.paymentsDetails(request)
|
||||
}
|
||||
|
||||
/**
|
||||
* Captures an Adyen payment
|
||||
* @param {object} payment - payment to capture
|
||||
* @returns {string} status = processing_captures
|
||||
*/
|
||||
async capturePayment(payment) {
|
||||
const { pspReference, merchantReference } = payment.data
|
||||
const { amount, currency_code } = payment
|
||||
|
||||
try {
|
||||
const captured = await this.adyenPaymentApi.post("/capture", {
|
||||
originalReference: pspReference,
|
||||
modificationAmount: amount,
|
||||
modificationAmount: {
|
||||
value: amount,
|
||||
currency: currency_code.toUpperCase(),
|
||||
},
|
||||
merchantAccount: this.options_.merchant_account,
|
||||
reference: merchantReference,
|
||||
})
|
||||
|
||||
if (
|
||||
@@ -170,40 +368,55 @@ class AdyenService extends BaseService {
|
||||
)
|
||||
}
|
||||
|
||||
return captured
|
||||
return { originalReference: pspReference, ...captured.data }
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Refunds an Ayden payment
|
||||
* @param {Object} paymentData - payment data to refund
|
||||
* Refunds an Adyen payment
|
||||
* @param {object} payment - payment to refund
|
||||
* @param {number} amountToRefund - amount to refund
|
||||
* @returns {Object} payment data result of refund
|
||||
* @returns {object} payment data result of refund
|
||||
*/
|
||||
async refundPayment(data) {
|
||||
const { pspReference, amount } = data
|
||||
async refundPayment(payment, amountToRefund) {
|
||||
const { originalReference, merchantReference } = payment.data
|
||||
const { currency_code } = payment
|
||||
|
||||
const refundAmount = {
|
||||
currency: currency_code.toUpperCase(),
|
||||
value: amountToRefund,
|
||||
}
|
||||
|
||||
try {
|
||||
return this.adyenPaymentApi.post("/refund", {
|
||||
originalReference: pspReference,
|
||||
const refunded = await this.adyenPaymentApi.post("/refund", {
|
||||
originalReference,
|
||||
merchantAccount: this.options_.merchant_account,
|
||||
modificationAmount: amount,
|
||||
modificationAmount: refundAmount,
|
||||
reference: merchantReference,
|
||||
})
|
||||
|
||||
return { originalReference, ...refunded.data }
|
||||
} catch (error) {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancels an Ayden payment
|
||||
* @param {Object} paymentData - payment data to cancel
|
||||
* @returns {Object} payment data result of cancel
|
||||
* Adyen does not have a way of deleting payments, hence the empty impl.
|
||||
*/
|
||||
async cancelPayment(paymentData) {
|
||||
const { pspReference } = paymentData
|
||||
async deletePayment(_) {
|
||||
return {}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancels an Adyen payment.
|
||||
* @param {object} payment - payment to cancel
|
||||
* @returns {object} payment data result of cancel
|
||||
*/
|
||||
async cancelPayment(payment) {
|
||||
const { pspReference } = payment.data
|
||||
|
||||
try {
|
||||
return this.adyenPaymentApi.post("/cancel", {
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
import _ from "lodash"
|
||||
import https from "https"
|
||||
import fs from "fs"
|
||||
import axios from "axios"
|
||||
import { PaymentService } from "medusa-interfaces"
|
||||
|
||||
class ApplePayAdyenService extends PaymentService {
|
||||
static identifier = "applepay-adyen"
|
||||
|
||||
constructor({ adyenService }, options) {
|
||||
super()
|
||||
|
||||
this.adyenService_ = adyenService
|
||||
|
||||
this.options_ = options
|
||||
}
|
||||
|
||||
/**
|
||||
* Status for Adyen payment.
|
||||
* @param {Object} paymentData - payment method data from cart
|
||||
* @returns {string} the status of the payment
|
||||
*/
|
||||
async getStatus(paymentData) {
|
||||
const { resultCode } = paymentData
|
||||
let status = "initial"
|
||||
|
||||
if (resultCode === "Authorised") {
|
||||
status = "authorized"
|
||||
}
|
||||
|
||||
return status
|
||||
}
|
||||
|
||||
async createPayment(_) {
|
||||
return {}
|
||||
}
|
||||
|
||||
async getApplePaySession(validationUrl) {
|
||||
let certificate
|
||||
try {
|
||||
// Place certificate in root folder
|
||||
certificate = fs.readFileSync("./apple-pay-cert.pem")
|
||||
} catch (error) {
|
||||
throw new Error(
|
||||
"Could not find ApplePay certificate. Make sure to place it in root folder of your server"
|
||||
)
|
||||
}
|
||||
|
||||
const httpsAgent = new https.Agent({
|
||||
cert: certificate,
|
||||
key: certificate,
|
||||
rejectUnauthorized: false,
|
||||
})
|
||||
|
||||
const request = {
|
||||
merchantIdentifier: this.options_.applepay_merchant_id,
|
||||
displayName: this.options_.applepay_display_name,
|
||||
initiative: "web",
|
||||
initiativeContext: this.options_.applepay_initiative_context,
|
||||
}
|
||||
|
||||
return axios.post(validationUrl, request, {
|
||||
httpsAgent,
|
||||
})
|
||||
}
|
||||
|
||||
async authorizePayment(sessionData, context) {
|
||||
return this.adyenService_.authorizePayment(sessionData, context)
|
||||
}
|
||||
|
||||
async getPaymentData(data) {
|
||||
return this.adyenService_.getPaymentData(data)
|
||||
}
|
||||
|
||||
async retrievePayment(data) {
|
||||
return this.adyenService_.retrievePayment(data)
|
||||
}
|
||||
|
||||
async updatePaymentData(sessionData, update) {
|
||||
return this.adyenService_.updatePaymentData(sessionData, update)
|
||||
}
|
||||
|
||||
async updatePayment(data, _) {
|
||||
return this.adyenService_.updatePayment(data)
|
||||
}
|
||||
|
||||
async deletePayment(data) {
|
||||
return this.adyenService_.deletePayment(data)
|
||||
}
|
||||
|
||||
async capturePayment(data) {
|
||||
return this.adyenService_.capturePayment(data)
|
||||
}
|
||||
|
||||
async refundPayment(data, amountToRefund) {
|
||||
return this.adyenService_.refundPayment(data, amountToRefund)
|
||||
}
|
||||
|
||||
async cancelPayment(data) {
|
||||
return this.adyenService_.cancelPayment(data)
|
||||
}
|
||||
}
|
||||
|
||||
export default ApplePayAdyenService
|
||||
@@ -1,74 +0,0 @@
|
||||
import _ from "lodash"
|
||||
import { PaymentService } from "medusa-interfaces"
|
||||
|
||||
class ApplePayAdyenService extends PaymentService {
|
||||
static identifier = "applepayAdyen"
|
||||
|
||||
constructor({ adyenService }) {
|
||||
super()
|
||||
|
||||
this.adyenService_ = adyenService
|
||||
}
|
||||
|
||||
/**
|
||||
* Status for Adyen payment.
|
||||
* @param {Object} paymentData - payment method data from cart
|
||||
* @returns {string} the status of the payment
|
||||
*/
|
||||
async getStatus(paymentData) {
|
||||
const { resultCode } = paymentData
|
||||
let status = "initial"
|
||||
|
||||
if (resultCode === "Authorised") {
|
||||
status = "authorized"
|
||||
}
|
||||
|
||||
return status
|
||||
}
|
||||
|
||||
async createPayment(_) {
|
||||
return {}
|
||||
}
|
||||
|
||||
async authorizePayment(cart, paymentMethod) {
|
||||
return this.adyenService_.authorizePayment(cart, paymentMethod)
|
||||
}
|
||||
|
||||
async retrievePayment(data) {
|
||||
return this.adyenService_.retrievePayment(data)
|
||||
}
|
||||
|
||||
async updatePayment(data, _) {
|
||||
return this.adyenService_.updatePayment(data)
|
||||
}
|
||||
|
||||
async deletePayment(data) {
|
||||
return this.adyenService_.deletePayment(data)
|
||||
}
|
||||
|
||||
async capturePayment(data) {
|
||||
try {
|
||||
return this.adyenService_.capturePayment(data)
|
||||
} catch (error) {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
async refundPayment(data) {
|
||||
try {
|
||||
return this.adyenService_.refundPayment(data)
|
||||
} catch (error) {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
async cancelPayment(data) {
|
||||
try {
|
||||
return this.adyenService_.cancelPayment(data)
|
||||
} catch (error) {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default ApplePayAdyenService
|
||||
@@ -0,0 +1,62 @@
|
||||
import _ from "lodash"
|
||||
import { PaymentService } from "medusa-interfaces"
|
||||
|
||||
class CardAdyenService extends PaymentService {
|
||||
static identifier = "scheme-adyen"
|
||||
|
||||
constructor({ adyenService }) {
|
||||
super()
|
||||
|
||||
this.adyenService_ = adyenService
|
||||
}
|
||||
|
||||
async retrieveSavedMethods(customer) {
|
||||
return this.adyenService_.retrieveSavedMethods(customer)
|
||||
}
|
||||
|
||||
async getStatus(paymentData) {
|
||||
return this.adyenService_.getStatus(paymentData)
|
||||
}
|
||||
|
||||
async createPayment(data) {
|
||||
return this.adyenService_.createPayment(data)
|
||||
}
|
||||
|
||||
async authorizePayment(sessionData, context) {
|
||||
return this.adyenService_.authorizePayment(sessionData, context)
|
||||
}
|
||||
|
||||
async retrievePayment(data) {
|
||||
return this.adyenService_.retrievePayment(data)
|
||||
}
|
||||
|
||||
async getPaymentData(data) {
|
||||
return this.adyenService_.getPaymentData(data)
|
||||
}
|
||||
|
||||
async updatePayment(data, _) {
|
||||
return this.adyenService_.updatePayment(data)
|
||||
}
|
||||
|
||||
async updatePaymentData(sessionData, update) {
|
||||
return this.adyenService_.updatePaymentData(sessionData, update)
|
||||
}
|
||||
|
||||
async deletePayment(data) {
|
||||
return this.adyenService_.deletePayment(data)
|
||||
}
|
||||
|
||||
async capturePayment(data) {
|
||||
return this.adyenService_.capturePayment(data)
|
||||
}
|
||||
|
||||
async refundPayment(data, amountToRefund) {
|
||||
return this.adyenService_.refundPayment(data, amountToRefund)
|
||||
}
|
||||
|
||||
async cancelPayment(data) {
|
||||
return this.adyenService_.cancelPayment(data)
|
||||
}
|
||||
}
|
||||
|
||||
export default CardAdyenService
|
||||
@@ -1,74 +0,0 @@
|
||||
import _ from "lodash"
|
||||
import { PaymentService } from "medusa-interfaces"
|
||||
|
||||
class CardAdyenService extends PaymentService {
|
||||
static identifier = "schemeAdyen"
|
||||
|
||||
constructor({ adyenService }) {
|
||||
super()
|
||||
|
||||
this.adyenService_ = adyenService
|
||||
}
|
||||
|
||||
/**
|
||||
* Status for Adyen payment.
|
||||
* @param {Object} paymentData - payment method data from cart
|
||||
* @returns {string} the status of the payment
|
||||
*/
|
||||
async getStatus(paymentData) {
|
||||
const { resultCode } = paymentData
|
||||
let status = "initial"
|
||||
|
||||
if (resultCode === "Authorised") {
|
||||
status = "authorized"
|
||||
}
|
||||
|
||||
return status
|
||||
}
|
||||
|
||||
async createPayment(_) {
|
||||
return {}
|
||||
}
|
||||
|
||||
async authorizePayment(cart, paymentMethod) {
|
||||
return this.adyenService_.authorizePayment(cart, paymentMethod)
|
||||
}
|
||||
|
||||
async retrievePayment(data) {
|
||||
return this.adyenService_.retrievePayment(data)
|
||||
}
|
||||
|
||||
async updatePayment(data, _) {
|
||||
return this.adyenService_.updatePayment(data)
|
||||
}
|
||||
|
||||
async deletePayment(data) {
|
||||
return this.adyenService_.deletePayment(data)
|
||||
}
|
||||
|
||||
async capturePayment(data) {
|
||||
try {
|
||||
return this.adyenService_.capturePayment(data)
|
||||
} catch (error) {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
async refundPayment(data) {
|
||||
try {
|
||||
return this.adyenService_.refundPayment(data)
|
||||
} catch (error) {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
async cancelPayment(data) {
|
||||
try {
|
||||
return this.adyenService_.cancelPayment(data)
|
||||
} catch (error) {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default CardAdyenService
|
||||
@@ -0,0 +1,58 @@
|
||||
import _ from "lodash"
|
||||
import { PaymentService } from "medusa-interfaces"
|
||||
|
||||
class GooglePayAdyenService extends PaymentService {
|
||||
static identifier = "paywithgoogle-adyen"
|
||||
|
||||
constructor({ adyenService }) {
|
||||
super()
|
||||
|
||||
this.adyenService_ = adyenService
|
||||
}
|
||||
|
||||
async getStatus(paymentData) {
|
||||
return this.adyenService_.getStatus(paymentData)
|
||||
}
|
||||
|
||||
async createPayment(data) {
|
||||
return this.adyenService_.createPayment(data)
|
||||
}
|
||||
|
||||
async authorizePayment(sessionData, context) {
|
||||
return this.adyenService_.authorizePayment(sessionData, context)
|
||||
}
|
||||
|
||||
async retrievePayment(data) {
|
||||
return this.adyenService_.retrievePayment(data)
|
||||
}
|
||||
|
||||
async updatePayment(data, _) {
|
||||
return this.adyenService_.updatePayment(data)
|
||||
}
|
||||
|
||||
async updatePaymentData(sessionData, update) {
|
||||
return this.adyenService_.updatePaymentData(sessionData, update)
|
||||
}
|
||||
|
||||
async getPaymentData(data) {
|
||||
return this.adyenService_.getPaymentData(data)
|
||||
}
|
||||
|
||||
async deletePayment(data) {
|
||||
return this.adyenService_.deletePayment(data)
|
||||
}
|
||||
|
||||
async capturePayment(data) {
|
||||
return this.adyenService_.capturePayment(data)
|
||||
}
|
||||
|
||||
async refundPayment(data, amountToRefund) {
|
||||
return this.adyenService_.refundPayment(data, amountToRefund)
|
||||
}
|
||||
|
||||
async cancelPayment(data) {
|
||||
return this.adyenService_.cancelPayment(data)
|
||||
}
|
||||
}
|
||||
|
||||
export default GooglePayAdyenService
|
||||
@@ -1,74 +0,0 @@
|
||||
import _ from "lodash"
|
||||
import { PaymentService } from "medusa-interfaces"
|
||||
|
||||
class GooglePayAdyenService extends PaymentService {
|
||||
static identifier = "googlepayAdyen"
|
||||
|
||||
constructor({ adyenService }) {
|
||||
super()
|
||||
|
||||
this.adyenService_ = adyenService
|
||||
}
|
||||
|
||||
/**
|
||||
* Status for Adyen payment.
|
||||
* @param {Object} paymentData - payment method data from cart
|
||||
* @returns {string} the status of the payment
|
||||
*/
|
||||
async getStatus(paymentData) {
|
||||
const { resultCode } = paymentData
|
||||
let status = "initial"
|
||||
|
||||
if (resultCode === "Authorised") {
|
||||
status = "authorized"
|
||||
}
|
||||
|
||||
return status
|
||||
}
|
||||
|
||||
async createPayment(_) {
|
||||
return {}
|
||||
}
|
||||
|
||||
async authorizePayment(cart, paymentMethod) {
|
||||
return this.adyenService_.authorizePayment(cart, paymentMethod)
|
||||
}
|
||||
|
||||
async retrievePayment(data) {
|
||||
return this.adyenService_.retrievePayment(data)
|
||||
}
|
||||
|
||||
async updatePayment(data, _) {
|
||||
return this.adyenService_.updatePayment(data)
|
||||
}
|
||||
|
||||
async deletePayment(data) {
|
||||
return this.adyenService_.deletePayment(data)
|
||||
}
|
||||
|
||||
async capturePayment(data) {
|
||||
try {
|
||||
return this.adyenService_.capturePayment(data)
|
||||
} catch (error) {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
async refundPayment(data) {
|
||||
try {
|
||||
return this.adyenService_.refundPayment(data)
|
||||
} catch (error) {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
async cancelPayment(data) {
|
||||
try {
|
||||
return this.adyenService_.cancelPayment(data)
|
||||
} catch (error) {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default GooglePayAdyenService
|
||||
@@ -0,0 +1,62 @@
|
||||
import _ from "lodash"
|
||||
import { PaymentService } from "medusa-interfaces"
|
||||
|
||||
class IdealAdyenService extends PaymentService {
|
||||
static identifier = "ideal-adyen"
|
||||
|
||||
constructor({ adyenService }) {
|
||||
super()
|
||||
|
||||
this.adyenService_ = adyenService
|
||||
}
|
||||
|
||||
async getStatus(paymentData) {
|
||||
return this.adyenService_.getStatus(paymentData)
|
||||
}
|
||||
|
||||
async createPayment(data) {
|
||||
return this.adyenService_.createPayment(data)
|
||||
}
|
||||
|
||||
async authorizePayment(sessionData, context) {
|
||||
return this.adyenService_.authorizePayment(sessionData, context)
|
||||
}
|
||||
|
||||
async retrievePayment(data) {
|
||||
return this.adyenService_.retrievePayment(data)
|
||||
}
|
||||
|
||||
async getPaymentData(data) {
|
||||
return this.adyenService_.getPaymentData(data)
|
||||
}
|
||||
|
||||
async updatePayment(data, _) {
|
||||
return this.adyenService_.updatePayment(data)
|
||||
}
|
||||
|
||||
async updatePaymentData(sessionData, update) {
|
||||
return this.adyenService_.updatePaymentData(sessionData, update)
|
||||
}
|
||||
|
||||
async getPaymentData(data) {
|
||||
return this.adyenService_.getPaymentData(data)
|
||||
}
|
||||
|
||||
async deletePayment(data) {
|
||||
return this.adyenService_.deletePayment(data)
|
||||
}
|
||||
|
||||
async capturePayment(data) {
|
||||
return this.adyenService_.capturePayment(data)
|
||||
}
|
||||
|
||||
async refundPayment(data, amountToRefund) {
|
||||
return this.adyenService_.refundPayment(data, amountToRefund)
|
||||
}
|
||||
|
||||
async cancelPayment(data) {
|
||||
return this.adyenService_.cancelPayment(data)
|
||||
}
|
||||
}
|
||||
|
||||
export default IdealAdyenService
|
||||
@@ -1,74 +0,0 @@
|
||||
import _ from "lodash"
|
||||
import { PaymentService } from "medusa-interfaces"
|
||||
|
||||
class IdealAdyenService extends PaymentService {
|
||||
static identifier = "idealAdyen"
|
||||
|
||||
constructor({ adyenService }) {
|
||||
super()
|
||||
|
||||
this.adyenService_ = adyenService
|
||||
}
|
||||
|
||||
/**
|
||||
* Status for Adyen payment.
|
||||
* @param {Object} paymentData - payment method data from cart
|
||||
* @returns {string} the status of the payment
|
||||
*/
|
||||
async getStatus(paymentData) {
|
||||
const { resultCode } = paymentData
|
||||
let status = "initial"
|
||||
|
||||
if (resultCode === "Authorised") {
|
||||
status = "authorized"
|
||||
}
|
||||
|
||||
return status
|
||||
}
|
||||
|
||||
async createPayment(_) {
|
||||
return {}
|
||||
}
|
||||
|
||||
async authorizePayment(cart, paymentMethod) {
|
||||
return this.adyenService_.authorizePayment(cart, paymentMethod)
|
||||
}
|
||||
|
||||
async retrievePayment(data) {
|
||||
return this.adyenService_.retrievePayment(data)
|
||||
}
|
||||
|
||||
async updatePayment(data, _) {
|
||||
return this.adyenService_.updatePayment(data)
|
||||
}
|
||||
|
||||
async deletePayment(data) {
|
||||
return this.adyenService_.deletePayment(data)
|
||||
}
|
||||
|
||||
async capturePayment(data) {
|
||||
try {
|
||||
return this.adyenService_.capturePayment(data)
|
||||
} catch (error) {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
async refundPayment(data) {
|
||||
try {
|
||||
return this.adyenService_.refundPayment(data)
|
||||
} catch (error) {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
async cancelPayment(data) {
|
||||
try {
|
||||
return this.adyenService_.cancelPayment(data)
|
||||
} catch (error) {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default IdealAdyenService
|
||||
@@ -0,0 +1,58 @@
|
||||
import _ from "lodash"
|
||||
import { PaymentService } from "medusa-interfaces"
|
||||
|
||||
class MobilePayAdyenService extends PaymentService {
|
||||
static identifier = "mobilepay-adyen"
|
||||
|
||||
constructor({ adyenService }) {
|
||||
super()
|
||||
|
||||
this.adyenService_ = adyenService
|
||||
}
|
||||
|
||||
async getStatus(paymentData) {
|
||||
return this.adyenService_.getStatus(paymentData)
|
||||
}
|
||||
|
||||
async createPayment(data) {
|
||||
return this.adyenService_.createPayment(data)
|
||||
}
|
||||
|
||||
async authorizePayment(sessionData, context) {
|
||||
return this.adyenService_.authorizePayment(sessionData, context)
|
||||
}
|
||||
|
||||
async retrievePayment(data) {
|
||||
return this.adyenService_.retrievePayment(data)
|
||||
}
|
||||
|
||||
async updatePayment(data, _) {
|
||||
return this.adyenService_.updatePayment(data)
|
||||
}
|
||||
|
||||
async updatePaymentData(sessionData, update) {
|
||||
return this.adyenService_.updatePaymentData(sessionData, update)
|
||||
}
|
||||
|
||||
async getPaymentData(data) {
|
||||
return this.adyenService_.getPaymentData(data)
|
||||
}
|
||||
|
||||
async deletePayment(data) {
|
||||
return this.adyenService_.deletePayment(data)
|
||||
}
|
||||
|
||||
async capturePayment(data) {
|
||||
return this.adyenService_.capturePayment(data)
|
||||
}
|
||||
|
||||
async refundPayment(data, amountToRefund) {
|
||||
return this.adyenService_.refundPayment(data, amountToRefund)
|
||||
}
|
||||
|
||||
async cancelPayment(data) {
|
||||
return this.adyenService_.cancelPayment(data)
|
||||
}
|
||||
}
|
||||
|
||||
export default MobilePayAdyenService
|
||||
@@ -1,74 +0,0 @@
|
||||
import _ from "lodash"
|
||||
import { PaymentService } from "medusa-interfaces"
|
||||
|
||||
class MobilePayAdyenService extends PaymentService {
|
||||
static identifier = "mobilepayAdyen"
|
||||
|
||||
constructor({ adyenService }) {
|
||||
super()
|
||||
|
||||
this.adyenService_ = adyenService
|
||||
}
|
||||
|
||||
/**
|
||||
* Status for Adyen payment.
|
||||
* @param {Object} paymentData - payment method data from cart
|
||||
* @returns {string} the status of the payment
|
||||
*/
|
||||
async getStatus(paymentData) {
|
||||
const { resultCode } = paymentData
|
||||
// let status = "initial"
|
||||
|
||||
// if (resultCode === "Authorised") {
|
||||
// status = "authorized"
|
||||
// }
|
||||
|
||||
return "authorized"
|
||||
}
|
||||
|
||||
async createPayment(_) {
|
||||
return {}
|
||||
}
|
||||
|
||||
async authorizePayment(cart, paymentMethod) {
|
||||
return this.adyenService_.authorizePayment(cart, paymentMethod)
|
||||
}
|
||||
|
||||
async retrievePayment(data) {
|
||||
return this.adyenService_.retrievePayment(data)
|
||||
}
|
||||
|
||||
async updatePayment(data, _) {
|
||||
return this.adyenService_.updatePayment(data)
|
||||
}
|
||||
|
||||
async deletePayment(data) {
|
||||
return this.adyenService_.deletePayment(data)
|
||||
}
|
||||
|
||||
async capturePayment(data) {
|
||||
try {
|
||||
return this.adyenService_.capturePayment(data)
|
||||
} catch (error) {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
async refundPayment(data) {
|
||||
try {
|
||||
return this.adyenService_.refundPayment(data)
|
||||
} catch (error) {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
async cancelPayment(data) {
|
||||
try {
|
||||
return this.adyenService_.cancelPayment(data)
|
||||
} catch (error) {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default MobilePayAdyenService
|
||||
@@ -0,0 +1,58 @@
|
||||
import _ from "lodash"
|
||||
import { PaymentService } from "medusa-interfaces"
|
||||
|
||||
class PayPalAdyenService extends PaymentService {
|
||||
static identifier = "paypal-adyen"
|
||||
|
||||
constructor({ adyenService }) {
|
||||
super()
|
||||
|
||||
this.adyenService_ = adyenService
|
||||
}
|
||||
|
||||
async getStatus(paymentData) {
|
||||
return this.adyenService_.getStatus(paymentData)
|
||||
}
|
||||
|
||||
async createPayment(data) {
|
||||
return this.adyenService_.createPayment(data)
|
||||
}
|
||||
|
||||
async authorizePayment(sessionData, context) {
|
||||
return this.adyenService_.authorizePayment(sessionData, context)
|
||||
}
|
||||
|
||||
async retrievePayment(data) {
|
||||
return this.adyenService_.retrievePayment(data)
|
||||
}
|
||||
|
||||
async getPaymentData(data) {
|
||||
return this.adyenService_.getPaymentData(data)
|
||||
}
|
||||
|
||||
async updatePayment(data, _) {
|
||||
return this.adyenService_.updatePayment(data)
|
||||
}
|
||||
|
||||
async updatePaymentData(sessionData, update) {
|
||||
return this.adyenService_.updatePaymentData(sessionData, update)
|
||||
}
|
||||
|
||||
async deletePayment(data) {
|
||||
return this.adyenService_.deletePayment(data)
|
||||
}
|
||||
|
||||
async capturePayment(data) {
|
||||
return this.adyenService_.capturePayment(data)
|
||||
}
|
||||
|
||||
async refundPayment(data, amountToRefund) {
|
||||
return this.adyenService_.refundPayment(data, amountToRefund)
|
||||
}
|
||||
|
||||
async cancelPayment(data) {
|
||||
return this.adyenService_.cancelPayment(data)
|
||||
}
|
||||
}
|
||||
|
||||
export default PayPalAdyenService
|
||||
@@ -1,74 +0,0 @@
|
||||
import _ from "lodash"
|
||||
import { PaymentService } from "medusa-interfaces"
|
||||
|
||||
class PayPalAdyenService extends PaymentService {
|
||||
static identifier = "paypalAdyen"
|
||||
|
||||
constructor({ adyenService }) {
|
||||
super()
|
||||
|
||||
this.adyenService_ = adyenService
|
||||
}
|
||||
|
||||
/**
|
||||
* Status for Adyen payment.
|
||||
* @param {Object} paymentData - payment method data from cart
|
||||
* @returns {string} the status of the payment
|
||||
*/
|
||||
async getStatus(paymentData) {
|
||||
const { resultCode } = paymentData
|
||||
let status = "initial"
|
||||
|
||||
if (resultCode === "Authorised") {
|
||||
status = "authorized"
|
||||
}
|
||||
|
||||
return status
|
||||
}
|
||||
|
||||
async createPayment(_) {
|
||||
return {}
|
||||
}
|
||||
|
||||
async authorizePayment(cart, paymentMethod) {
|
||||
return this.adyenService_.authorizePayment(cart, paymentMethod)
|
||||
}
|
||||
|
||||
async retrievePayment(data) {
|
||||
return this.adyenService_.retrievePayment(data)
|
||||
}
|
||||
|
||||
async updatePayment(data, _) {
|
||||
return this.adyenService_.updatePayment(data)
|
||||
}
|
||||
|
||||
async deletePayment(data) {
|
||||
return this.adyenService_.deletePayment(data)
|
||||
}
|
||||
|
||||
async capturePayment(data) {
|
||||
try {
|
||||
return this.adyenService_.capturePayment(data)
|
||||
} catch (error) {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
async refundPayment(data) {
|
||||
try {
|
||||
return this.adyenService_.refundPayment(data)
|
||||
} catch (error) {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
async cancelPayment(data) {
|
||||
try {
|
||||
return this.adyenService_.cancelPayment(data)
|
||||
} catch (error) {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default PayPalAdyenService
|
||||
@@ -0,0 +1,159 @@
|
||||
class AdyenSubscriber {
|
||||
constructor(container) {
|
||||
/** @private @const {CartService} */
|
||||
this.cartService_ = container.cartService
|
||||
|
||||
/** @private @const {OrderService} */
|
||||
this.orderService_ = container.orderService
|
||||
|
||||
/** @private @const {EventBusService} */
|
||||
this.eventBus_ = container.eventBusService
|
||||
|
||||
/** @private @const {PaymentRepository} */
|
||||
this.paymentRepository_ = container.paymentRepository
|
||||
|
||||
this.manager_ = container.manager
|
||||
|
||||
this.eventBus_.subscribe(
|
||||
"adyen.notification_received",
|
||||
async (notification) => this.handleAdyenNotification(notification)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Webhook handler for Adyen payment.
|
||||
* @param {object} notification - webhook notification object
|
||||
* @returns {string} the status of the payment intent
|
||||
*/
|
||||
async handleAdyenNotification(notification) {
|
||||
switch (true) {
|
||||
case notification.success === "true" &&
|
||||
notification.eventCode === "AUTHORISATION": {
|
||||
this.handleAuthorization_(notification)
|
||||
break
|
||||
}
|
||||
case notification.success === "false" &&
|
||||
notification.eventCode === "AUTHORISATION": {
|
||||
this.handleFailedAuthorization_(notification)
|
||||
break
|
||||
}
|
||||
case notification.success === "true" &&
|
||||
notification.eventCode === "CAPTURE_FAILED": {
|
||||
this.handleFailedCapture_(notification)
|
||||
break
|
||||
}
|
||||
case notification.success === "false" &&
|
||||
notification.eventCode === "CAPTURE": {
|
||||
this.handleFailedCapture_(notification)
|
||||
break
|
||||
}
|
||||
case notification.success === "false" &&
|
||||
notification.eventCode === "REFUND": {
|
||||
this.handleFailedRefund_(notification)
|
||||
break
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
async handleFailedAuthorization_(notification) {
|
||||
const cartId = notification.additionalData["metadata.cart_id"]
|
||||
const cart = await this.cartService_.retrieve(cartId, {
|
||||
relations: ["payment_sessions"],
|
||||
})
|
||||
|
||||
const { payment_session } = cart
|
||||
|
||||
const updated = {
|
||||
...payment_session,
|
||||
status: "error",
|
||||
data: {
|
||||
...payment_session.data,
|
||||
pspReference: notification.pspReference,
|
||||
},
|
||||
}
|
||||
|
||||
await this.cartService_.updatePaymentSession(cart.id, updated)
|
||||
}
|
||||
|
||||
async handleAuthorization_(notification) {
|
||||
const cartId = notification.additionalData["metadata.cart_id"]
|
||||
const paymentRepository = this.manager_.getCustomRepository(
|
||||
this.paymentRepository_
|
||||
)
|
||||
|
||||
// We need to ensure, that an order is created in situations, where the
|
||||
// customer might have closed their browser prior to order creation
|
||||
try {
|
||||
const orderPayment = await paymentRepository.findOne({
|
||||
where: { cart_id: cartId },
|
||||
})
|
||||
|
||||
if (!orderPayment) {
|
||||
throw new Error("Payment not found")
|
||||
}
|
||||
|
||||
const updatedPayment = {
|
||||
...orderPayment,
|
||||
data: {
|
||||
...orderPayment.data,
|
||||
resultCode: "Authorised",
|
||||
pspReference: notification.pspReference,
|
||||
},
|
||||
}
|
||||
|
||||
await this.paymentRepository_.save(updatedPayment)
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
await this.manager_.transaction(async (manager) => {
|
||||
const session = {
|
||||
pspReference: notification.pspReference,
|
||||
resultCode: "Authorised",
|
||||
}
|
||||
|
||||
await this.cartService_
|
||||
.withTransaction(manager)
|
||||
.updatePaymentSession(cartId, session)
|
||||
|
||||
await this.cartService_
|
||||
.withTransaction(manager)
|
||||
.authorizePayment(cartId)
|
||||
|
||||
await this.orderService_.withTransaction(manager).createFromCart(cartId)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
async handleFailedCapture_(notification) {
|
||||
const cartId = notification.additionalData["metadata.cart_id"]
|
||||
|
||||
const order = await this.orderService_.retrieveByCartId(cartId)
|
||||
|
||||
await this.orderService_.update(order.id, {
|
||||
payment_status: "requires_action",
|
||||
})
|
||||
|
||||
await this.eventBus_.emit("order.payment_capture_failed", {
|
||||
order,
|
||||
error: new Error(`Adyen payment capture: ${notification.reason}`),
|
||||
})
|
||||
}
|
||||
|
||||
async handleFailedRefund_(notification) {
|
||||
const cartId = notification.additionalData["metadata.cart_id"]
|
||||
|
||||
const order = await this.orderService_.retrieveByCartId(cartId)
|
||||
|
||||
await this.orderService_.update(order.id, {
|
||||
payment_status: "requires_action",
|
||||
})
|
||||
|
||||
await this.eventBus_.emit("order.refund_failed", {
|
||||
order,
|
||||
error: new Error(`Adyen payment capture: ${notification.reason}`),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export default AdyenSubscriber
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "medusa-payment-klarna",
|
||||
"version": "1.0.14",
|
||||
"version": "1.0.13-alpha.176+0646bd3",
|
||||
"description": "Klarna Payment provider for Medusa Commerce",
|
||||
"main": "index.js",
|
||||
"repository": {
|
||||
@@ -40,8 +40,8 @@
|
||||
"axios": "^0.21.0",
|
||||
"body-parser": "^1.19.0",
|
||||
"express": "^4.17.1",
|
||||
"medusa-core-utils": "^1.0.11",
|
||||
"medusa-test-utils": "^1.0.13"
|
||||
"medusa-core-utils": "^1.0.12-alpha.787+0646bd3",
|
||||
"medusa-test-utils": "^1.0.12-alpha.176+0646bd3"
|
||||
},
|
||||
"gitHead": "3cc7cbe5124cbcbb75f6e1435db4dcfaa2a60408"
|
||||
"gitHead": "0646bd395a6056657cb0aa93c13699c4a9dbbcdd"
|
||||
}
|
||||
@@ -2,13 +2,17 @@ import { IdMap } from "medusa-test-utils"
|
||||
|
||||
export const carts = {
|
||||
frCart: {
|
||||
_id: IdMap.getId("fr-cart"),
|
||||
id: IdMap.getId("fr-cart"),
|
||||
email: "lebron@james.com",
|
||||
title: "test",
|
||||
region: {
|
||||
tax_rate: 2500,
|
||||
currency_code: "eur",
|
||||
},
|
||||
region_id: IdMap.getId("region-france"),
|
||||
items: [
|
||||
{
|
||||
_id: IdMap.getId("line"),
|
||||
id: IdMap.getId("line"),
|
||||
title: "merge line",
|
||||
description: "This is a new line",
|
||||
thumbnail: "test-img-yeah.com/thumb",
|
||||
@@ -16,20 +20,20 @@ export const carts = {
|
||||
{
|
||||
unit_price: 8,
|
||||
variant: {
|
||||
_id: IdMap.getId("eur-8-us-10"),
|
||||
id: IdMap.getId("eur-8-us-10"),
|
||||
},
|
||||
product: {
|
||||
_id: IdMap.getId("product"),
|
||||
id: IdMap.getId("product"),
|
||||
},
|
||||
quantity: 1,
|
||||
},
|
||||
{
|
||||
unit_price: 10,
|
||||
variant: {
|
||||
_id: IdMap.getId("eur-10-us-12"),
|
||||
id: IdMap.getId("eur-10-us-12"),
|
||||
},
|
||||
product: {
|
||||
_id: IdMap.getId("product"),
|
||||
id: IdMap.getId("product"),
|
||||
},
|
||||
quantity: 1,
|
||||
},
|
||||
@@ -37,17 +41,17 @@ export const carts = {
|
||||
quantity: 10,
|
||||
},
|
||||
{
|
||||
_id: IdMap.getId("existingLine"),
|
||||
id: IdMap.getId("existingLine"),
|
||||
title: "merge line",
|
||||
description: "This is a new line",
|
||||
thumbnail: "test-img-yeah.com/thumb",
|
||||
content: {
|
||||
unit_price: 10,
|
||||
variant: {
|
||||
_id: IdMap.getId("eur-10-us-12"),
|
||||
id: IdMap.getId("eur-10-us-12"),
|
||||
},
|
||||
product: {
|
||||
_id: IdMap.getId("product"),
|
||||
id: IdMap.getId("product"),
|
||||
},
|
||||
quantity: 1,
|
||||
},
|
||||
@@ -56,13 +60,16 @@ export const carts = {
|
||||
],
|
||||
shipping_methods: [
|
||||
{
|
||||
_id: IdMap.getId("freeShipping"),
|
||||
id: IdMap.getId("freeShipping"),
|
||||
data: {
|
||||
name: "test",
|
||||
},
|
||||
profile_id: "default_profile",
|
||||
},
|
||||
],
|
||||
shipping_options: [
|
||||
{
|
||||
_id: IdMap.getId("freeShipping"),
|
||||
id: IdMap.getId("freeShipping"),
|
||||
profile_id: "default_profile",
|
||||
},
|
||||
],
|
||||
@@ -87,7 +94,7 @@ export const carts = {
|
||||
discounts: [
|
||||
{
|
||||
code: "MEDUSA_FREE",
|
||||
discount_rule: {
|
||||
rule: {
|
||||
type: "percent",
|
||||
value: 20,
|
||||
allocation: "item",
|
||||
|
||||
@@ -3,38 +3,107 @@ export default async (req, res) => {
|
||||
const { shipping_address, merchant_data } = req.body
|
||||
|
||||
try {
|
||||
const manager = req.scope.resolve("manager")
|
||||
const cartService = req.scope.resolve("cartService")
|
||||
const klarnaProviderService = req.scope.resolve("pp_klarna")
|
||||
const shippingProfileService = req.scope.resolve("shippingProfileService")
|
||||
|
||||
const cart = await cartService.retrieve(merchant_data)
|
||||
const result = await manager.transaction("SERIALIZABLE", async (m) => {
|
||||
const cart = await cartService.retrieve(merchant_data, {
|
||||
select: ["subtotal"],
|
||||
relations: [
|
||||
"shipping_address",
|
||||
"billing_address",
|
||||
"region",
|
||||
"items",
|
||||
"shipping_methods",
|
||||
"shipping_methods.shipping_option",
|
||||
"items.variant",
|
||||
"items.variant.product",
|
||||
],
|
||||
})
|
||||
|
||||
if (shipping_address) {
|
||||
const updatedAddress = {
|
||||
first_name: shipping_address.given_name,
|
||||
last_name: shipping_address.family_name,
|
||||
address_1: shipping_address.street_address,
|
||||
address_2: shipping_address.street_address2,
|
||||
city: shipping_address.city,
|
||||
country_code: shipping_address.country.toUpperCase(),
|
||||
postal_code: shipping_address.postal_code,
|
||||
phone: shipping_address.phone
|
||||
if (shipping_address) {
|
||||
const shippingAddress = {
|
||||
...cart.shipping_address,
|
||||
first_name: shipping_address.given_name,
|
||||
last_name: shipping_address.family_name,
|
||||
address_1: shipping_address.street_address,
|
||||
address_2: shipping_address.street_address2,
|
||||
city: shipping_address.city,
|
||||
country_code: shipping_address.country,
|
||||
postal_code: shipping_address.postal_code,
|
||||
phone: shipping_address.phone,
|
||||
}
|
||||
|
||||
let billingAddress = {
|
||||
first_name: shipping_address.given_name,
|
||||
last_name: shipping_address.family_name,
|
||||
address_1: shipping_address.street_address,
|
||||
address_2: shipping_address.street_address2,
|
||||
city: shipping_address.city,
|
||||
country_code: shipping_address.country,
|
||||
postal_code: shipping_address.postal_code,
|
||||
phone: shipping_address.phone,
|
||||
}
|
||||
|
||||
if (cart.billing_address) {
|
||||
billingAddress = {
|
||||
...billingAddress,
|
||||
...cart.billing_address,
|
||||
}
|
||||
}
|
||||
|
||||
await cartService.update(cart.id, {
|
||||
shipping_address: shippingAddress,
|
||||
billing_address: billingAddress,
|
||||
email: shipping_address.email,
|
||||
})
|
||||
|
||||
const shippingOptions = await shippingProfileService.fetchCartOptions(
|
||||
cart
|
||||
)
|
||||
|
||||
if (shippingOptions?.length) {
|
||||
const option = shippingOptions.find(
|
||||
(o) => o.data && !o.data.require_drop_point
|
||||
)
|
||||
await cartService
|
||||
.withTransaction(m)
|
||||
.addShippingMethod(cart.id, option.id, option.data)
|
||||
}
|
||||
|
||||
// Fetch and return updated Klarna order
|
||||
const updatedCart = await cartService
|
||||
.withTransaction(m)
|
||||
.retrieve(cart.id, {
|
||||
select: [
|
||||
"gift_card_total",
|
||||
"subtotal",
|
||||
"total",
|
||||
"tax_total",
|
||||
"discount_total",
|
||||
"subtotal",
|
||||
],
|
||||
relations: [
|
||||
"shipping_address",
|
||||
"billing_address",
|
||||
"region",
|
||||
"shipping_methods",
|
||||
"shipping_methods.shipping_option",
|
||||
"items",
|
||||
"items.variant",
|
||||
"items.variant.product",
|
||||
],
|
||||
})
|
||||
return klarnaProviderService.cartToKlarnaOrder(updatedCart)
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
|
||||
await cartService.updateShippingAddress(cart._id, updatedAddress)
|
||||
await cartService.updateBillingAddress(cart._id, updatedAddress)
|
||||
await cartService.updateEmail(cart._id, shipping_address.email)
|
||||
})
|
||||
|
||||
const shippingOptions = await shippingProfileService.fetchCartOptions(cart)
|
||||
if (shippingOptions.length === 1) {
|
||||
const option = shippingOptions[0]
|
||||
await cartService.addShippingMethod(cart._id, option._id, option.data)
|
||||
}
|
||||
|
||||
// Fetch and return updated Klarna order
|
||||
const updatedCart = await cartService.retrieve(cart._id)
|
||||
const order = await klarnaProviderService.cartToKlarnaOrder(updatedCart)
|
||||
res.json(order)
|
||||
if (result) {
|
||||
res.json(result)
|
||||
return
|
||||
} else {
|
||||
res.sendStatus(400)
|
||||
|
||||
@@ -4,7 +4,6 @@ export default async (req, res) => {
|
||||
const { klarna_order_id } = req.query
|
||||
|
||||
try {
|
||||
const cartService = req.scope.resolve("cartService")
|
||||
const orderService = req.scope.resolve("orderService")
|
||||
const klarnaProviderService = req.scope.resolve("pp_klarna")
|
||||
|
||||
@@ -13,22 +12,9 @@ export default async (req, res) => {
|
||||
)
|
||||
|
||||
const cartId = klarnaOrder.merchant_data
|
||||
try {
|
||||
const order = await orderService.retrieveByCartId(cartId)
|
||||
await klarnaProviderService.acknowledgeOrder(
|
||||
klarnaOrder.order_id,
|
||||
order._id
|
||||
)
|
||||
} catch (err) {
|
||||
if (err.type === MedusaError.Types.NOT_FOUND) {
|
||||
const cart = await cartService.retrieve(cartId)
|
||||
const order = await orderService.createFromCart(cart)
|
||||
await klarnaProviderService.acknowledgeOrder(
|
||||
klarnaOrder.order_id,
|
||||
order._id
|
||||
)
|
||||
}
|
||||
}
|
||||
const order = await orderService.retrieveByCartId(cartId)
|
||||
|
||||
await klarnaProviderService.acknowledgeOrder(klarnaOrder.order_id, order.id)
|
||||
|
||||
res.sendStatus(200)
|
||||
} catch (error) {
|
||||
|
||||
@@ -7,19 +7,57 @@ export default async (req, res) => {
|
||||
const klarnaProviderService = req.scope.resolve("pp_klarna")
|
||||
const shippingProfileService = req.scope.resolve("shippingProfileService")
|
||||
|
||||
const cart = await cartService.retrieve(merchant_data)
|
||||
const shippingOptions = await shippingProfileService.fetchCartOptions(cart)
|
||||
const cart = await cartService.retrieve(merchant_data, {
|
||||
select: ["subtotal"],
|
||||
relations: [
|
||||
"shipping_address",
|
||||
"billing_address",
|
||||
"region",
|
||||
"shipping_methods",
|
||||
"shipping_methods.shipping_option",
|
||||
"items",
|
||||
"items.variant",
|
||||
"items.variant.product",
|
||||
],
|
||||
})
|
||||
let shippingOptions = await shippingProfileService.fetchCartOptions(cart)
|
||||
|
||||
shippingOptions = shippingOptions.filter(
|
||||
(so) => !so.data?.require_drop_point
|
||||
)
|
||||
|
||||
const ids = selected_shipping_option.id.split(".")
|
||||
await Promise.all(ids.map(async id => {
|
||||
const option = shippingOptions.find(({ _id }) => _id.equals(id))
|
||||
await Promise.all(
|
||||
ids.map(async (id) => {
|
||||
const option = shippingOptions.find((so) => so.id === id)
|
||||
|
||||
if (option) {
|
||||
await cartService.addShippingMethod(cart._id, option._id, option.data)
|
||||
}
|
||||
}))
|
||||
if (option) {
|
||||
await cartService.addShippingMethod(cart.id, option.id, option.data)
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
const newCart = await cartService.retrieve(cart.id, {
|
||||
select: [
|
||||
"gift_card_total",
|
||||
"subtotal",
|
||||
"total",
|
||||
"tax_total",
|
||||
"discount_total",
|
||||
"subtotal",
|
||||
],
|
||||
relations: [
|
||||
"shipping_address",
|
||||
"billing_address",
|
||||
"shipping_methods",
|
||||
"shipping_methods.shipping_option",
|
||||
"region",
|
||||
"items",
|
||||
"items.variant",
|
||||
"items.variant.product",
|
||||
],
|
||||
})
|
||||
|
||||
const newCart = await cartService.retrieve(cart._id)
|
||||
const order = await klarnaProviderService.cartToKlarnaOrder(newCart)
|
||||
res.json(order)
|
||||
} catch (error) {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
jest.unmock("axios")
|
||||
import axios from "axios"
|
||||
import MockAdapter from "axios-mock-adapter"
|
||||
|
||||
import KlarnaProviderService from "../klarna-provider"
|
||||
@@ -151,7 +153,6 @@ describe("KlarnaProviderService", () => {
|
||||
|
||||
expect(result).toEqual({
|
||||
order_id: "123456789",
|
||||
order_amount: 1000,
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -176,15 +177,19 @@ describe("KlarnaProviderService", () => {
|
||||
mockServer
|
||||
.onPost("/ordermanagement/v1/orders/123456789/cancel")
|
||||
.reply(() => {
|
||||
return [200]
|
||||
return [200, { order_id: "123456789" }]
|
||||
})
|
||||
|
||||
it("returns order id", async () => {
|
||||
mockServer.onGet("/ordermanagement/v1/orders/123456789").reply(() => {
|
||||
return [200, { order_id: "123456789" }]
|
||||
})
|
||||
|
||||
it("returns order", async () => {
|
||||
result = await klarnaProviderService.cancelPayment({
|
||||
order_id: "123456789",
|
||||
data: { order_id: "123456789" },
|
||||
})
|
||||
|
||||
expect(result).toEqual("123456789")
|
||||
expect(result).toEqual({ order_id: "123456789" })
|
||||
})
|
||||
})
|
||||
|
||||
@@ -289,15 +294,19 @@ describe("KlarnaProviderService", () => {
|
||||
mockServer
|
||||
.onPost("/ordermanagement/v1/orders/123456789/captures")
|
||||
.reply(() => {
|
||||
return [200]
|
||||
return [200, { order_id: "123456789" }]
|
||||
})
|
||||
|
||||
it("returns order id", async () => {
|
||||
mockServer.onGet("/ordermanagement/v1/orders/123456789").reply(() => {
|
||||
return [200, { order_id: "123456789" }]
|
||||
})
|
||||
|
||||
it("returns order", async () => {
|
||||
result = await klarnaProviderService.capturePayment({
|
||||
order_id: "123456789",
|
||||
data: { order_id: "123456789" },
|
||||
})
|
||||
|
||||
expect(result).toEqual("123456789")
|
||||
expect(result).toEqual({ order_id: "123456789" })
|
||||
})
|
||||
})
|
||||
|
||||
@@ -322,18 +331,22 @@ describe("KlarnaProviderService", () => {
|
||||
mockServer
|
||||
.onPost("/ordermanagement/v1/orders/123456789/refunds")
|
||||
.reply(() => {
|
||||
return [200]
|
||||
return [200, { order_id: "123456789" }]
|
||||
})
|
||||
|
||||
it("returns order id", async () => {
|
||||
mockServer.onGet("/ordermanagement/v1/orders/123456789").reply(() => {
|
||||
return [200, { order_id: "123456789" }]
|
||||
})
|
||||
|
||||
it("returns order", async () => {
|
||||
result = await klarnaProviderService.refundPayment(
|
||||
{
|
||||
order_id: "123456789",
|
||||
data: { order_id: "123456789" },
|
||||
},
|
||||
1000
|
||||
)
|
||||
|
||||
expect(result).toEqual("123456789")
|
||||
expect(result).toEqual({ order_id: "123456789" })
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -5,14 +5,26 @@ import { PaymentService } from "medusa-interfaces"
|
||||
class KlarnaProviderService extends PaymentService {
|
||||
static identifier = "klarna"
|
||||
|
||||
constructor(
|
||||
{ shippingProfileService, totalsService, regionService },
|
||||
options
|
||||
) {
|
||||
constructor({ shippingProfileService }, options) {
|
||||
super()
|
||||
|
||||
/**
|
||||
* Required Klarna options:
|
||||
* {
|
||||
* backend_url: "",
|
||||
* url: "",
|
||||
* user: "",
|
||||
* password: "",
|
||||
* merchant_urls: {
|
||||
* terms: ``,
|
||||
* checkout: ``,
|
||||
* confirmation: ``,
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
this.options_ = options
|
||||
|
||||
/** @private @const {Klarna} */
|
||||
this.klarna_ = axios.create({
|
||||
baseURL: options.url,
|
||||
auth: {
|
||||
@@ -27,54 +39,36 @@ class KlarnaProviderService extends PaymentService {
|
||||
|
||||
this.backendUrl_ = options.backend_url
|
||||
|
||||
this.totalsService_ = totalsService
|
||||
|
||||
this.regionService_ = regionService
|
||||
|
||||
/** @private @const {ShippingProfileService} */
|
||||
this.shippingProfileService_ = shippingProfileService
|
||||
}
|
||||
|
||||
async lineItemsToOrderLines_(cart, taxRate) {
|
||||
let order_lines = []
|
||||
|
||||
const tax = taxRate / 100
|
||||
|
||||
cart.items.forEach((item) => {
|
||||
// For bundles, we create an order line for each item in the bundle
|
||||
if (Array.isArray(item.content)) {
|
||||
item.content.forEach((c) => {
|
||||
const total_amount = c.unit_price * c.quantity * (taxRate + 1)
|
||||
const total_tax_amount = total_amount * taxRate
|
||||
// Withdraw discount from the total item amount
|
||||
const quantity = item.quantity
|
||||
const unit_price = item.unit_price * (tax + 1)
|
||||
const total_amount = unit_price * quantity
|
||||
const total_tax_amount = total_amount * (tax / (1 + tax))
|
||||
|
||||
order_lines.push({
|
||||
name: item.title,
|
||||
unit_price: c.unit_price,
|
||||
quantity: c.quantity,
|
||||
tax_rate: taxRate * 10000,
|
||||
total_amount,
|
||||
total_tax_amount,
|
||||
})
|
||||
})
|
||||
} else {
|
||||
// Withdraw discount from the total item amount
|
||||
const quantity = item.quantity
|
||||
const unit_price = item.content.unit_price * 100 * (taxRate + 1)
|
||||
const total_amount = unit_price * quantity
|
||||
const total_tax_amount = total_amount * (taxRate / (1 + taxRate))
|
||||
|
||||
order_lines.push({
|
||||
name: item.title,
|
||||
tax_rate: taxRate * 10000,
|
||||
quantity,
|
||||
unit_price,
|
||||
total_amount,
|
||||
total_tax_amount,
|
||||
})
|
||||
}
|
||||
order_lines.push({
|
||||
name: item.title,
|
||||
tax_rate: tax * 10000,
|
||||
quantity,
|
||||
unit_price,
|
||||
total_amount,
|
||||
total_tax_amount,
|
||||
})
|
||||
})
|
||||
|
||||
if (cart.shipping_methods.length) {
|
||||
const { name, price } = cart.shipping_methods.reduce(
|
||||
(acc, next) => {
|
||||
acc.name = [...acc.name, next.name]
|
||||
acc.name = [...acc.name, next.data.name]
|
||||
acc.price += next.price
|
||||
return acc
|
||||
},
|
||||
@@ -85,10 +79,10 @@ class KlarnaProviderService extends PaymentService {
|
||||
name: name.join(" + "),
|
||||
quantity: 1,
|
||||
type: "shipping_fee",
|
||||
unit_price: price * (1 + taxRate) * 100,
|
||||
tax_rate: taxRate * 10000,
|
||||
total_amount: price * (1 + taxRate) * 100,
|
||||
total_tax_amount: price * taxRate * 100,
|
||||
unit_price: price * (1 + tax),
|
||||
tax_rate: tax * 10000,
|
||||
total_amount: price * (1 + tax),
|
||||
total_tax_amount: price * tax,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -98,28 +92,39 @@ class KlarnaProviderService extends PaymentService {
|
||||
async cartToKlarnaOrder(cart) {
|
||||
let order = {
|
||||
// Cart id is stored, such that we can use it for hooks
|
||||
merchant_data: cart._id,
|
||||
// TODO: Investigate if other locales are needed
|
||||
merchant_data: cart.id,
|
||||
locale: "en-US",
|
||||
}
|
||||
|
||||
const { tax_rate, currency_code } = await this.regionService_.retrieve(
|
||||
cart.region_id
|
||||
)
|
||||
const { region, gift_card_total, discount_total, tax_total, total } = cart
|
||||
|
||||
order.order_lines = await this.lineItemsToOrderLines_(cart, tax_rate)
|
||||
const taxRate = region.tax_rate / 100
|
||||
|
||||
const discount = (await this.totalsService_.getDiscountTotal(cart)) * 100
|
||||
if (discount) {
|
||||
order.order_lines = await this.lineItemsToOrderLines_(cart, region.tax_rate)
|
||||
|
||||
if (discount_total) {
|
||||
order.order_lines.push({
|
||||
name: `Discount`,
|
||||
quantity: 1,
|
||||
type: "discount",
|
||||
unit_price: 0,
|
||||
total_discount_amount: discount * (1 + tax_rate),
|
||||
tax_rate: tax_rate * 10000,
|
||||
total_amount: -discount * (1 + tax_rate),
|
||||
total_tax_amount: -discount * tax_rate,
|
||||
total_discount_amount: discount_total * (1 + taxRate),
|
||||
tax_rate: taxRate * 10000,
|
||||
total_amount: -discount_total * (1 + taxRate),
|
||||
total_tax_amount: -discount_total * taxRate,
|
||||
})
|
||||
}
|
||||
|
||||
if (gift_card_total) {
|
||||
order.order_lines.push({
|
||||
name: `Gift Card`,
|
||||
quantity: 1,
|
||||
type: "gift_card",
|
||||
unit_price: 0,
|
||||
total_discount_amount: gift_card_total * (1 + taxRate),
|
||||
tax_rate: taxRate * 10000,
|
||||
total_amount: -gift_card_total * (1 + taxRate),
|
||||
total_tax_amount: -gift_card_total * taxRate,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -134,20 +139,19 @@ class KlarnaProviderService extends PaymentService {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Check if country matches ISO
|
||||
if (
|
||||
!_.isEmpty(cart.shipping_address) &&
|
||||
cart.shipping_address.country_code
|
||||
) {
|
||||
order.purchase_country = cart.shipping_address.country_code
|
||||
const hasCountry =
|
||||
!_.isEmpty(cart.shipping_address) && cart.shipping_address.country_code
|
||||
|
||||
if (hasCountry) {
|
||||
order.purchase_country = cart.shipping_address.country_code.toUpperCase()
|
||||
} else {
|
||||
// Defaults to Sweden
|
||||
order.purchase_country = "SE"
|
||||
}
|
||||
order.order_amount = (await this.totalsService_.getTotal(cart)) * 100
|
||||
order.order_tax_amount = (await this.totalsService_.getTaxTotal(cart)) * 100
|
||||
// TODO: Check if currency matches ISO
|
||||
order.purchase_currency = currency_code
|
||||
|
||||
order.order_amount = total
|
||||
order.order_tax_amount = tax_total
|
||||
order.purchase_currency = region.currency_code.toUpperCase()
|
||||
|
||||
order.merchant_urls = {
|
||||
terms: this.options_.merchant_urls.terms,
|
||||
@@ -159,20 +163,24 @@ class KlarnaProviderService extends PaymentService {
|
||||
}
|
||||
|
||||
if (cart.shipping_address && cart.shipping_address.first_name) {
|
||||
const shippingOptions = await this.shippingProfileService_.fetchCartOptions(
|
||||
let shippingOptions = await this.shippingProfileService_.fetchCartOptions(
|
||||
cart
|
||||
)
|
||||
|
||||
shippingOptions = shippingOptions.filter(
|
||||
(so) => !so.data?.require_drop_point
|
||||
)
|
||||
|
||||
// If the cart does not have shipping methods yet, preselect one from
|
||||
// shipping_options and set the selected shipping method
|
||||
if (cart.shipping_methods.length) {
|
||||
const shipping_method = cart.shipping_methods[0]
|
||||
order.selected_shipping_option = {
|
||||
id: shipping_method._id,
|
||||
name: shipping_method.name,
|
||||
price: shipping_method.price * (1 + tax_rate) * 100,
|
||||
tax_amount: shipping_method.price * tax_rate * 100,
|
||||
tax_rate: tax_rate * 10000,
|
||||
id: shipping_method.shipping_option.id,
|
||||
name: shipping_method.shipping_option.name,
|
||||
price: shipping_method.price * (1 + taxRate),
|
||||
tax_amount: shipping_method.price * taxRate,
|
||||
tax_rate: taxRate * 10000,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -185,20 +193,25 @@ class KlarnaProviderService extends PaymentService {
|
||||
return acc
|
||||
}, {})
|
||||
|
||||
let f = (a, b) =>
|
||||
[].concat(...a.map((a) => b.map((b) => [].concat(a, b))))
|
||||
let cartesian = (a, b, ...c) => (b ? cartesian(f(a, b), ...c) : a)
|
||||
// Helper function that calculates the cartesian product of multiple arrays
|
||||
// Don't touch :D
|
||||
// From: https://stackoverflow.com/questions/12303989/cartesian-product-of-multiple-arrays-in-javascript
|
||||
const f = (a, b) =>
|
||||
[].concat(...a.map((d) => b.map((e) => [].concat(d, e))))
|
||||
const cartesian = (a, b, ...c) => (b ? cartesian(f(a, b), ...c) : a)
|
||||
|
||||
const methods = Object.keys(partitioned).map((k) => partitioned[k])
|
||||
const combinations = cartesian(...methods)
|
||||
|
||||
// Use the cartesian product of shipping methods to generate correct
|
||||
// format for the Klarna Widget
|
||||
order.shipping_options = combinations.map((combination) => {
|
||||
combination = Array.isArray(combination) ? combination : [combination]
|
||||
const details = combination.reduce(
|
||||
(acc, next) => {
|
||||
acc.id = [...acc.id, next._id]
|
||||
acc.id = [...acc.id, next.id]
|
||||
acc.name = [...acc.name, next.name]
|
||||
acc.price += next.price
|
||||
acc.price += next.amount
|
||||
return acc
|
||||
},
|
||||
{ id: [], name: [], price: 0 }
|
||||
@@ -207,10 +220,9 @@ class KlarnaProviderService extends PaymentService {
|
||||
return {
|
||||
id: details.id.join("."),
|
||||
name: details.name.join(" + "),
|
||||
price: details.price * (1 + tax_rate) * 100,
|
||||
tax_amount: details.price * tax_rate * 100,
|
||||
tax_rate: tax_rate * 10000,
|
||||
preselected: combinations.length === 1,
|
||||
price: details.price * (1 + taxRate),
|
||||
tax_amount: details.price * taxRate,
|
||||
tax_rate: taxRate * 10000,
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -224,20 +236,18 @@ class KlarnaProviderService extends PaymentService {
|
||||
* @returns {string} the status of the Klarna order
|
||||
*/
|
||||
async getStatus(paymentData) {
|
||||
try {
|
||||
const { order_id } = paymentData
|
||||
const { data: order } = await this.klarna_.get(
|
||||
`${this.klarnaOrderUrl_}/${order_id}`
|
||||
)
|
||||
const { order_id } = paymentData
|
||||
const { data: order } = await this.klarna_.get(
|
||||
`${this.klarnaOrderUrl_}/${order_id}`
|
||||
)
|
||||
|
||||
let status = "initial"
|
||||
if (order.status === "checkout_complete") {
|
||||
status = "authorized"
|
||||
}
|
||||
return status
|
||||
} catch (error) {
|
||||
throw error
|
||||
let status = "pending"
|
||||
|
||||
if (order.status === "checkout_complete") {
|
||||
status = "authorized"
|
||||
}
|
||||
|
||||
return status
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -249,9 +259,12 @@ class KlarnaProviderService extends PaymentService {
|
||||
async createPayment(cart) {
|
||||
try {
|
||||
const order = await this.cartToKlarnaOrder(cart)
|
||||
return this.klarna_
|
||||
|
||||
const klarnaPayment = await this.klarna_
|
||||
.post(this.klarnaOrderUrl_, order)
|
||||
.then(({ data }) => data)
|
||||
|
||||
return klarnaPayment
|
||||
} catch (error) {
|
||||
throw error
|
||||
}
|
||||
@@ -272,6 +285,21 @@ class KlarnaProviderService extends PaymentService {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a Klarna payment objec.
|
||||
* @param {object} sessionData - the data of the payment to retrieve
|
||||
* @returns {Promise<object>} Stripe payment intent
|
||||
*/
|
||||
async getPaymentData(sessionData) {
|
||||
try {
|
||||
return this.klarna_
|
||||
.get(`${this.klarnaOrderUrl_}/${sessionData.data.order_id}`)
|
||||
.then(({ data }) => data)
|
||||
} catch (error) {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves completed Klarna Order.
|
||||
* @param {string} klarnaOrderId - id of the order to retrieve
|
||||
@@ -287,6 +315,23 @@ class KlarnaProviderService extends PaymentService {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Authorizes Klarna payment by simply returning the status for the payment
|
||||
* in use.
|
||||
* @param {object} sessionData - payment session data
|
||||
* @param {object} context - properties relevant to current context
|
||||
* @returns {Promise<{ status: string, data: object }>} result with data and status
|
||||
*/
|
||||
async authorizePayment(sessionData, context = {}) {
|
||||
try {
|
||||
const paymentStatus = await this.getStatus(sessionData.data)
|
||||
|
||||
return { data: sessionData.data, status: paymentStatus }
|
||||
} catch (error) {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Acknowledges a Klarna order as part of the order completion process
|
||||
* @param {string} klarnaOrderId - id of the order to acknowledge
|
||||
@@ -332,6 +377,14 @@ class KlarnaProviderService extends PaymentService {
|
||||
}
|
||||
}
|
||||
|
||||
async updatePaymentData(sessionData, update) {
|
||||
try {
|
||||
return { ...sessionData, ...update }
|
||||
} catch (error) {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates Klarna order.
|
||||
* @param {string} order - the order to update
|
||||
@@ -339,14 +392,14 @@ class KlarnaProviderService extends PaymentService {
|
||||
* @returns {Object} updated order
|
||||
*/
|
||||
async updatePayment(paymentData, cart) {
|
||||
try {
|
||||
const order = await this.cartToKlarnaOrder(cart, true)
|
||||
if (cart.total !== paymentData.order_amount) {
|
||||
const order = await this.cartToKlarnaOrder(cart)
|
||||
return this.klarna_
|
||||
.post(`${this.klarnaOrderUrl_}/${paymentData.order_id}`, order)
|
||||
.then(({ data }) => data)
|
||||
} catch (error) {
|
||||
throw error
|
||||
}
|
||||
|
||||
return paymentData
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -354,9 +407,9 @@ class KlarnaProviderService extends PaymentService {
|
||||
* @param {Object} paymentData - payment method data from cart
|
||||
* @returns {string} id of captured order
|
||||
*/
|
||||
async capturePayment(paymentData) {
|
||||
async capturePayment(payment) {
|
||||
const { order_id } = payment.data
|
||||
try {
|
||||
const { order_id } = paymentData
|
||||
const { data: order } = await this.klarna_.get(
|
||||
`${this.klarnaOrderManagementUrl_}/${order_id}`
|
||||
)
|
||||
@@ -368,7 +421,8 @@ class KlarnaProviderService extends PaymentService {
|
||||
captured_amount: order_amount,
|
||||
}
|
||||
)
|
||||
return order_id
|
||||
|
||||
return this.retrieveCompletedOrder(order_id)
|
||||
} catch (error) {
|
||||
throw error
|
||||
}
|
||||
@@ -379,16 +433,17 @@ class KlarnaProviderService extends PaymentService {
|
||||
* @param {Object} paymentData - payment method data from cart
|
||||
* @returns {string} id of refunded order
|
||||
*/
|
||||
async refundPayment(paymentData, amount) {
|
||||
async refundPayment(payment, amountToRefund) {
|
||||
const { order_id } = payment.data
|
||||
try {
|
||||
const { order_id } = paymentData
|
||||
await this.klarna_.post(
|
||||
`${this.klarnaOrderManagementUrl_}/${order_id}/refunds`,
|
||||
{
|
||||
refunded_amount: amount * 100,
|
||||
refunded_amount: amountToRefund,
|
||||
}
|
||||
)
|
||||
return order_id
|
||||
|
||||
return this.retrieveCompletedOrder(order_id)
|
||||
} catch (error) {
|
||||
throw error
|
||||
}
|
||||
@@ -399,17 +454,22 @@ class KlarnaProviderService extends PaymentService {
|
||||
* @param {Object} paymentData - payment method data from cart
|
||||
* @returns {string} id of cancelled order
|
||||
*/
|
||||
async cancelPayment(paymentData) {
|
||||
async cancelPayment(payment) {
|
||||
const { order_id } = payment.data
|
||||
try {
|
||||
const { order_id } = paymentData
|
||||
await this.klarna_.post(
|
||||
`${this.klarnaOrderManagementUrl_}/${order_id}/cancel`
|
||||
)
|
||||
return order_id
|
||||
|
||||
return this.retrieveCompletedOrder(order_id)
|
||||
} catch (error) {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
async deletePayment(_) {
|
||||
return Promise.resolve()
|
||||
}
|
||||
}
|
||||
|
||||
export default KlarnaProviderService
|
||||
@@ -3595,7 +3595,7 @@ joi-objectid@^3.0.1:
|
||||
resolved "https://registry.yarnpkg.com/joi-objectid/-/joi-objectid-3.0.1.tgz#63ace7860f8e1a993a28d40c40ffd8eff01a3668"
|
||||
integrity sha512-V/3hbTlGpvJ03Me6DJbdBI08hBTasFOmipsauOsxOSnsF1blxV537WTl1zPwbfcKle4AK0Ma4OPnzMH4LlvTpQ==
|
||||
|
||||
joi@^17.2.1:
|
||||
joi@^17.2.1, joi@^17.3.0:
|
||||
version "17.3.0"
|
||||
resolved "https://registry.yarnpkg.com/joi/-/joi-17.3.0.tgz#f1be4a6ce29bc1716665819ac361dfa139fff5d2"
|
||||
integrity sha512-Qh5gdU6niuYbUIUV5ejbsMiiFmBdw8Kcp8Buj2JntszCkCfxJ9Cz76OtHxOZMPXrt5810iDIXs+n1nNVoquHgg==
|
||||
@@ -3857,21 +3857,29 @@ media-typer@0.3.0:
|
||||
resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748"
|
||||
integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=
|
||||
|
||||
medusa-core-utils@^1.0.10:
|
||||
version "1.0.10"
|
||||
resolved "https://registry.yarnpkg.com/medusa-core-utils/-/medusa-core-utils-1.0.10.tgz#6175f1e428318205742621f78d387254fc85b8a1"
|
||||
integrity sha512-z12ITzPl5UpDJTILNXcMGD4yujSRkuvVtxkrvqCmA44IEyRj1hWZ0dGbVzuHj2wIahDHFFg66oq91IQH5QVsyg==
|
||||
medusa-core-utils@^1.0.11:
|
||||
version "1.0.11"
|
||||
resolved "https://registry.yarnpkg.com/medusa-core-utils/-/medusa-core-utils-1.0.11.tgz#c77685c4b637f6aebe808e82dc4f7739c2f9ff79"
|
||||
integrity sha512-Nnp7RCWsR4MJNrslC0YB/wMO1Wf2QJH0aras+tjRdymXfR/ySn2/0+v8pyXxhY8XXYzOZknnjdCB8KI9+PHlsA==
|
||||
dependencies:
|
||||
joi "^17.2.1"
|
||||
joi-objectid "^3.0.1"
|
||||
|
||||
medusa-test-utils@^1.0.11:
|
||||
version "1.0.11"
|
||||
resolved "https://registry.yarnpkg.com/medusa-test-utils/-/medusa-test-utils-1.0.11.tgz#bae901efa90426fb64818de700dc6e163820160f"
|
||||
integrity sha512-CSNb70sXOfKTndzxWtPMYq+KeYaFkSLAPUyhuwDNVvkbne0faOYF5OMCV8r3aLoQ3D1Tvjrb/XB9Qt17ycjuPw==
|
||||
medusa-core-utils@^1.0.12-alpha.787+0646bd3:
|
||||
version "1.0.12-alpha.801"
|
||||
resolved "https://registry.yarnpkg.com/medusa-core-utils/-/medusa-core-utils-1.0.12-alpha.801.tgz#0a7d0b5d4b2290d9a2f7afb35ab1f0bec666dbb0"
|
||||
integrity sha512-1ppxgl8noZfO3JLLHeLj4eEMOwTI/LcCxWwwD7AUp5uP1brPmctthDTM/pOMrEuytKWIqaNXCRohvAlgDr0vYA==
|
||||
dependencies:
|
||||
joi "^17.3.0"
|
||||
joi-objectid "^3.0.1"
|
||||
|
||||
medusa-test-utils@^1.0.12-alpha.176+0646bd3:
|
||||
version "1.0.13"
|
||||
resolved "https://registry.yarnpkg.com/medusa-test-utils/-/medusa-test-utils-1.0.13.tgz#f728b8dacb1ba64d85529e3f2e9dba6c7e315416"
|
||||
integrity sha512-EBbjGLHFEG/Fnqneire/bNCPeseo1ZFF3n3yMUfp0JDmBAVgE8L52wJa8Zeu36TL/rbQsQAsTAS6La46A8IJgg==
|
||||
dependencies:
|
||||
"@babel/plugin-transform-classes" "^7.9.5"
|
||||
medusa-core-utils "^1.0.10"
|
||||
medusa-core-utils "^1.0.11"
|
||||
mongoose "^5.8.0"
|
||||
|
||||
memory-pager@^1.0.2:
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"plugins": [
|
||||
"@babel/plugin-proposal-optional-chaining",
|
||||
"@babel/plugin-proposal-class-properties",
|
||||
"@babel/plugin-transform-instanceof",
|
||||
"@babel/plugin-transform-classes"
|
||||
|
||||
@@ -12,5 +12,4 @@ yarn.lock
|
||||
/services
|
||||
/models
|
||||
/subscribers
|
||||
/__mocks__
|
||||
|
||||
/__mocks__
|
||||
@@ -5,5 +5,9 @@ node_modules
|
||||
/*.js
|
||||
!index.js
|
||||
yarn.lock
|
||||
|
||||
src
|
||||
.gitignore
|
||||
.eslintrc
|
||||
.babelrc
|
||||
.prettierrc
|
||||
|
||||
@@ -7,13 +7,17 @@ exports["default"] = exports.CartServiceMock = exports.carts = void 0;
|
||||
|
||||
var _medusaTestUtils = require("medusa-test-utils");
|
||||
|
||||
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
|
||||
|
||||
var carts = {
|
||||
emptyCart: {
|
||||
_id: _medusaTestUtils.IdMap.getId("emptyCart"),
|
||||
id: _medusaTestUtils.IdMap.getId("emptyCart"),
|
||||
items: [],
|
||||
region_id: _medusaTestUtils.IdMap.getId("testRegion"),
|
||||
customer_id: "test-customer",
|
||||
payment_sessions: [],
|
||||
shipping_options: [{
|
||||
_id: _medusaTestUtils.IdMap.getId("freeShipping"),
|
||||
id: _medusaTestUtils.IdMap.getId("freeShipping"),
|
||||
profile_id: "default_profile",
|
||||
data: {
|
||||
some_data: "yes"
|
||||
@@ -21,58 +25,53 @@ var carts = {
|
||||
}]
|
||||
},
|
||||
frCart: {
|
||||
_id: _medusaTestUtils.IdMap.getId("fr-cart"),
|
||||
id: _medusaTestUtils.IdMap.getId("fr-cart"),
|
||||
email: "lebron@james.com",
|
||||
title: "test",
|
||||
region_id: _medusaTestUtils.IdMap.getId("region-france"),
|
||||
items: [{
|
||||
_id: _medusaTestUtils.IdMap.getId("line"),
|
||||
id: _medusaTestUtils.IdMap.getId("line"),
|
||||
title: "merge line",
|
||||
description: "This is a new line",
|
||||
thumbnail: "test-img-yeah.com/thumb",
|
||||
content: [{
|
||||
unit_price: 8,
|
||||
variant: {
|
||||
_id: _medusaTestUtils.IdMap.getId("eur-8-us-10")
|
||||
},
|
||||
product: {
|
||||
_id: _medusaTestUtils.IdMap.getId("product")
|
||||
},
|
||||
quantity: 1
|
||||
}, {
|
||||
unit_price: 10,
|
||||
variant: {
|
||||
_id: _medusaTestUtils.IdMap.getId("eur-10-us-12")
|
||||
},
|
||||
product: {
|
||||
_id: _medusaTestUtils.IdMap.getId("product")
|
||||
},
|
||||
quantity: 1
|
||||
}],
|
||||
quantity: 10
|
||||
}, {
|
||||
_id: _medusaTestUtils.IdMap.getId("existingLine"),
|
||||
title: "merge line",
|
||||
description: "This is a new line",
|
||||
thumbnail: "test-img-yeah.com/thumb",
|
||||
content: {
|
||||
unit_price: 10,
|
||||
variant: {
|
||||
_id: _medusaTestUtils.IdMap.getId("eur-10-us-12")
|
||||
},
|
||||
product: {
|
||||
_id: _medusaTestUtils.IdMap.getId("product")
|
||||
},
|
||||
quantity: 1
|
||||
unit_price: 8,
|
||||
variant: {
|
||||
id: _medusaTestUtils.IdMap.getId("eur-8-us-10")
|
||||
},
|
||||
product: {
|
||||
id: _medusaTestUtils.IdMap.getId("product")
|
||||
},
|
||||
// {
|
||||
// unit_price: 10,
|
||||
// variant: {
|
||||
// id: IdMap.getId("eur-10-us-12"),
|
||||
// },
|
||||
// product: {
|
||||
// id: IdMap.getId("product"),
|
||||
// },
|
||||
// quantity: 1,
|
||||
// },
|
||||
quantity: 10
|
||||
}],
|
||||
}, _defineProperty({
|
||||
id: _medusaTestUtils.IdMap.getId("existingLine"),
|
||||
title: "merge line",
|
||||
description: "This is a new line",
|
||||
thumbnail: "test-img-yeah.com/thumb",
|
||||
unit_price: 10,
|
||||
variant: {
|
||||
id: _medusaTestUtils.IdMap.getId("eur-10-us-12")
|
||||
},
|
||||
product: {
|
||||
id: _medusaTestUtils.IdMap.getId("product")
|
||||
},
|
||||
quantity: 1
|
||||
}, "quantity", 10)],
|
||||
shipping_methods: [{
|
||||
_id: _medusaTestUtils.IdMap.getId("freeShipping"),
|
||||
id: _medusaTestUtils.IdMap.getId("freeShipping"),
|
||||
profile_id: "default_profile"
|
||||
}],
|
||||
shipping_options: [{
|
||||
_id: _medusaTestUtils.IdMap.getId("freeShipping"),
|
||||
id: _medusaTestUtils.IdMap.getId("freeShipping"),
|
||||
profile_id: "default_profile"
|
||||
}],
|
||||
payment_sessions: [{
|
||||
@@ -95,57 +94,57 @@ var carts = {
|
||||
customer_id: _medusaTestUtils.IdMap.getId("lebron")
|
||||
},
|
||||
frCartNoStripeCustomer: {
|
||||
_id: _medusaTestUtils.IdMap.getId("fr-cart-no-customer"),
|
||||
id: _medusaTestUtils.IdMap.getId("fr-cart-no-customer"),
|
||||
title: "test",
|
||||
region_id: _medusaTestUtils.IdMap.getId("region-france"),
|
||||
items: [{
|
||||
_id: _medusaTestUtils.IdMap.getId("line"),
|
||||
id: _medusaTestUtils.IdMap.getId("line"),
|
||||
title: "merge line",
|
||||
description: "This is a new line",
|
||||
thumbnail: "test-img-yeah.com/thumb",
|
||||
content: [{
|
||||
unit_price: 8,
|
||||
variant: {
|
||||
_id: _medusaTestUtils.IdMap.getId("eur-8-us-10")
|
||||
id: _medusaTestUtils.IdMap.getId("eur-8-us-10")
|
||||
},
|
||||
product: {
|
||||
_id: _medusaTestUtils.IdMap.getId("product")
|
||||
id: _medusaTestUtils.IdMap.getId("product")
|
||||
},
|
||||
quantity: 1
|
||||
}, {
|
||||
unit_price: 10,
|
||||
variant: {
|
||||
_id: _medusaTestUtils.IdMap.getId("eur-10-us-12")
|
||||
id: _medusaTestUtils.IdMap.getId("eur-10-us-12")
|
||||
},
|
||||
product: {
|
||||
_id: _medusaTestUtils.IdMap.getId("product")
|
||||
id: _medusaTestUtils.IdMap.getId("product")
|
||||
},
|
||||
quantity: 1
|
||||
}],
|
||||
quantity: 10
|
||||
}, {
|
||||
_id: _medusaTestUtils.IdMap.getId("existingLine"),
|
||||
id: _medusaTestUtils.IdMap.getId("existingLine"),
|
||||
title: "merge line",
|
||||
description: "This is a new line",
|
||||
thumbnail: "test-img-yeah.com/thumb",
|
||||
content: {
|
||||
unit_price: 10,
|
||||
variant: {
|
||||
_id: _medusaTestUtils.IdMap.getId("eur-10-us-12")
|
||||
id: _medusaTestUtils.IdMap.getId("eur-10-us-12")
|
||||
},
|
||||
product: {
|
||||
_id: _medusaTestUtils.IdMap.getId("product")
|
||||
id: _medusaTestUtils.IdMap.getId("product")
|
||||
},
|
||||
quantity: 1
|
||||
},
|
||||
quantity: 10
|
||||
}],
|
||||
shipping_methods: [{
|
||||
_id: _medusaTestUtils.IdMap.getId("freeShipping"),
|
||||
id: _medusaTestUtils.IdMap.getId("freeShipping"),
|
||||
profile_id: "default_profile"
|
||||
}],
|
||||
shipping_options: [{
|
||||
_id: _medusaTestUtils.IdMap.getId("freeShipping"),
|
||||
id: _medusaTestUtils.IdMap.getId("freeShipping"),
|
||||
profile_id: "default_profile"
|
||||
}],
|
||||
payment_sessions: [{
|
||||
@@ -175,6 +174,10 @@ var CartServiceMock = {
|
||||
return Promise.resolve(carts.frCart);
|
||||
}
|
||||
|
||||
if (cartId === _medusaTestUtils.IdMap.getId("fr-cart-no-customer")) {
|
||||
return Promise.resolve(carts.frCartNoStripeCustomer);
|
||||
}
|
||||
|
||||
if (cartId === _medusaTestUtils.IdMap.getId("emptyCart")) {
|
||||
return Promise.resolve(carts.emptyCart);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "medusa-payment-stripe",
|
||||
"version": "1.0.15",
|
||||
"version": "1.0.14-alpha.176+0646bd3",
|
||||
"description": "Stripe Payment provider for Meduas Commerce",
|
||||
"main": "index.js",
|
||||
"repository": {
|
||||
@@ -15,6 +15,7 @@
|
||||
"@babel/core": "^7.7.5",
|
||||
"@babel/node": "^7.7.4",
|
||||
"@babel/plugin-proposal-class-properties": "^7.7.4",
|
||||
"@babel/plugin-proposal-optional-chaining": "^7.12.7",
|
||||
"@babel/plugin-transform-classes": "^7.9.5",
|
||||
"@babel/plugin-transform-instanceof": "^7.8.3",
|
||||
"@babel/plugin-transform-runtime": "^7.7.6",
|
||||
@@ -25,7 +26,7 @@
|
||||
"cross-env": "^5.2.1",
|
||||
"eslint": "^6.8.0",
|
||||
"jest": "^25.5.2",
|
||||
"medusa-test-utils": "^1.0.13"
|
||||
"medusa-test-utils": "^1.0.12-alpha.176+0646bd3"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "babel src -d . --ignore **/__tests__",
|
||||
@@ -39,8 +40,8 @@
|
||||
"dependencies": {
|
||||
"body-parser": "^1.19.0",
|
||||
"express": "^4.17.1",
|
||||
"medusa-core-utils": "^1.0.11",
|
||||
"medusa-core-utils": "^1.0.12-alpha.787+0646bd3",
|
||||
"stripe": "^8.50.0"
|
||||
},
|
||||
"gitHead": "3cc7cbe5124cbcbb75f6e1435db4dcfaa2a60408"
|
||||
"gitHead": "0646bd395a6056657cb0aa93c13699c4a9dbbcdd"
|
||||
}
|
||||
@@ -2,12 +2,14 @@ import { IdMap } from "medusa-test-utils"
|
||||
|
||||
export const carts = {
|
||||
emptyCart: {
|
||||
_id: IdMap.getId("emptyCart"),
|
||||
id: IdMap.getId("emptyCart"),
|
||||
items: [],
|
||||
region_id: IdMap.getId("testRegion"),
|
||||
customer_id: "test-customer",
|
||||
payment_sessions: [],
|
||||
shipping_options: [
|
||||
{
|
||||
_id: IdMap.getId("freeShipping"),
|
||||
id: IdMap.getId("freeShipping"),
|
||||
profile_id: "default_profile",
|
||||
data: {
|
||||
some_data: "yes",
|
||||
@@ -16,67 +18,61 @@ export const carts = {
|
||||
],
|
||||
},
|
||||
frCart: {
|
||||
_id: IdMap.getId("fr-cart"),
|
||||
id: IdMap.getId("fr-cart"),
|
||||
email: "lebron@james.com",
|
||||
title: "test",
|
||||
region_id: IdMap.getId("region-france"),
|
||||
items: [
|
||||
{
|
||||
_id: IdMap.getId("line"),
|
||||
id: IdMap.getId("line"),
|
||||
title: "merge line",
|
||||
description: "This is a new line",
|
||||
thumbnail: "test-img-yeah.com/thumb",
|
||||
content: [
|
||||
{
|
||||
unit_price: 8,
|
||||
variant: {
|
||||
_id: IdMap.getId("eur-8-us-10"),
|
||||
},
|
||||
product: {
|
||||
_id: IdMap.getId("product"),
|
||||
},
|
||||
quantity: 1,
|
||||
},
|
||||
{
|
||||
unit_price: 10,
|
||||
variant: {
|
||||
_id: IdMap.getId("eur-10-us-12"),
|
||||
},
|
||||
product: {
|
||||
_id: IdMap.getId("product"),
|
||||
},
|
||||
quantity: 1,
|
||||
},
|
||||
],
|
||||
|
||||
unit_price: 8,
|
||||
variant: {
|
||||
id: IdMap.getId("eur-8-us-10"),
|
||||
},
|
||||
product: {
|
||||
id: IdMap.getId("product"),
|
||||
},
|
||||
// {
|
||||
// unit_price: 10,
|
||||
// variant: {
|
||||
// id: IdMap.getId("eur-10-us-12"),
|
||||
// },
|
||||
// product: {
|
||||
// id: IdMap.getId("product"),
|
||||
// },
|
||||
// quantity: 1,
|
||||
// },
|
||||
quantity: 10,
|
||||
},
|
||||
{
|
||||
_id: IdMap.getId("existingLine"),
|
||||
id: IdMap.getId("existingLine"),
|
||||
title: "merge line",
|
||||
description: "This is a new line",
|
||||
thumbnail: "test-img-yeah.com/thumb",
|
||||
content: {
|
||||
unit_price: 10,
|
||||
variant: {
|
||||
_id: IdMap.getId("eur-10-us-12"),
|
||||
},
|
||||
product: {
|
||||
_id: IdMap.getId("product"),
|
||||
},
|
||||
quantity: 1,
|
||||
unit_price: 10,
|
||||
variant: {
|
||||
id: IdMap.getId("eur-10-us-12"),
|
||||
},
|
||||
product: {
|
||||
id: IdMap.getId("product"),
|
||||
},
|
||||
quantity: 1,
|
||||
quantity: 10,
|
||||
},
|
||||
],
|
||||
shipping_methods: [
|
||||
{
|
||||
_id: IdMap.getId("freeShipping"),
|
||||
id: IdMap.getId("freeShipping"),
|
||||
profile_id: "default_profile",
|
||||
},
|
||||
],
|
||||
shipping_options: [
|
||||
{
|
||||
_id: IdMap.getId("freeShipping"),
|
||||
id: IdMap.getId("freeShipping"),
|
||||
profile_id: "default_profile",
|
||||
},
|
||||
],
|
||||
@@ -102,12 +98,12 @@ export const carts = {
|
||||
customer_id: IdMap.getId("lebron"),
|
||||
},
|
||||
frCartNoStripeCustomer: {
|
||||
_id: IdMap.getId("fr-cart-no-customer"),
|
||||
id: IdMap.getId("fr-cart-no-customer"),
|
||||
title: "test",
|
||||
region_id: IdMap.getId("region-france"),
|
||||
items: [
|
||||
{
|
||||
_id: IdMap.getId("line"),
|
||||
id: IdMap.getId("line"),
|
||||
title: "merge line",
|
||||
description: "This is a new line",
|
||||
thumbnail: "test-img-yeah.com/thumb",
|
||||
@@ -115,20 +111,20 @@ export const carts = {
|
||||
{
|
||||
unit_price: 8,
|
||||
variant: {
|
||||
_id: IdMap.getId("eur-8-us-10"),
|
||||
id: IdMap.getId("eur-8-us-10"),
|
||||
},
|
||||
product: {
|
||||
_id: IdMap.getId("product"),
|
||||
id: IdMap.getId("product"),
|
||||
},
|
||||
quantity: 1,
|
||||
},
|
||||
{
|
||||
unit_price: 10,
|
||||
variant: {
|
||||
_id: IdMap.getId("eur-10-us-12"),
|
||||
id: IdMap.getId("eur-10-us-12"),
|
||||
},
|
||||
product: {
|
||||
_id: IdMap.getId("product"),
|
||||
id: IdMap.getId("product"),
|
||||
},
|
||||
quantity: 1,
|
||||
},
|
||||
@@ -136,17 +132,17 @@ export const carts = {
|
||||
quantity: 10,
|
||||
},
|
||||
{
|
||||
_id: IdMap.getId("existingLine"),
|
||||
id: IdMap.getId("existingLine"),
|
||||
title: "merge line",
|
||||
description: "This is a new line",
|
||||
thumbnail: "test-img-yeah.com/thumb",
|
||||
content: {
|
||||
unit_price: 10,
|
||||
variant: {
|
||||
_id: IdMap.getId("eur-10-us-12"),
|
||||
id: IdMap.getId("eur-10-us-12"),
|
||||
},
|
||||
product: {
|
||||
_id: IdMap.getId("product"),
|
||||
id: IdMap.getId("product"),
|
||||
},
|
||||
quantity: 1,
|
||||
},
|
||||
@@ -155,13 +151,13 @@ export const carts = {
|
||||
],
|
||||
shipping_methods: [
|
||||
{
|
||||
_id: IdMap.getId("freeShipping"),
|
||||
id: IdMap.getId("freeShipping"),
|
||||
profile_id: "default_profile",
|
||||
},
|
||||
],
|
||||
shipping_options: [
|
||||
{
|
||||
_id: IdMap.getId("freeShipping"),
|
||||
id: IdMap.getId("freeShipping"),
|
||||
profile_id: "default_profile",
|
||||
},
|
||||
],
|
||||
@@ -193,6 +189,9 @@ export const CartServiceMock = {
|
||||
if (cartId === IdMap.getId("fr-cart")) {
|
||||
return Promise.resolve(carts.frCart)
|
||||
}
|
||||
if (cartId === IdMap.getId("fr-cart-no-customer")) {
|
||||
return Promise.resolve(carts.frCartNoStripeCustomer)
|
||||
}
|
||||
if (cartId === IdMap.getId("emptyCart")) {
|
||||
return Promise.resolve(carts.emptyCart)
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ export default async (req, res) => {
|
||||
switch (event.type) {
|
||||
case "payment_intent.succeeded":
|
||||
if (order) {
|
||||
await orderService.update(order._id, {
|
||||
await orderService.update(order.id, {
|
||||
payment_status: "captured",
|
||||
})
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ export const StripeProviderServiceMock = {
|
||||
}
|
||||
if (payData.id === "pi_no") {
|
||||
return Promise.resolve({
|
||||
id: "pi",
|
||||
id: "pi_no",
|
||||
})
|
||||
}
|
||||
return Promise.resolve(undefined)
|
||||
|
||||
@@ -129,6 +129,7 @@ describe("StripeProviderService", () => {
|
||||
result = await stripeProviderService.updatePayment(
|
||||
{
|
||||
id: "pi_lebron",
|
||||
amount: 800,
|
||||
},
|
||||
{
|
||||
total: 1000,
|
||||
@@ -136,7 +137,7 @@ describe("StripeProviderService", () => {
|
||||
)
|
||||
})
|
||||
|
||||
it("returns cancelled stripe payment intent", () => {
|
||||
it("returns updated stripe payment intent", () => {
|
||||
expect(result).toEqual({
|
||||
id: "pi_lebron",
|
||||
customer: "cus_lebron",
|
||||
@@ -186,7 +187,13 @@ describe("StripeProviderService", () => {
|
||||
}
|
||||
)
|
||||
|
||||
result = await stripeProviderService.capturePayment("pi_lebron")
|
||||
result = await stripeProviderService.capturePayment({
|
||||
data: {
|
||||
id: "pi_lebron",
|
||||
customer: "cus_lebron",
|
||||
amount: 1000,
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it("returns captured stripe payment intent", () => {
|
||||
@@ -210,7 +217,17 @@ describe("StripeProviderService", () => {
|
||||
}
|
||||
)
|
||||
|
||||
result = await stripeProviderService.refundPayment("pi_lebron", 1000)
|
||||
result = await stripeProviderService.refundPayment(
|
||||
{
|
||||
data: {
|
||||
id: "re_123",
|
||||
payment_intent: "pi_lebron",
|
||||
amount: 1000,
|
||||
status: "succeeded",
|
||||
},
|
||||
},
|
||||
1000
|
||||
)
|
||||
})
|
||||
|
||||
it("returns refunded stripe payment intent", () => {
|
||||
@@ -234,7 +251,13 @@ describe("StripeProviderService", () => {
|
||||
}
|
||||
)
|
||||
|
||||
result = await stripeProviderService.cancelPayment("pi_lebron")
|
||||
result = await stripeProviderService.cancelPayment({
|
||||
data: {
|
||||
id: "pi_lebron",
|
||||
customer: "cus_lebron",
|
||||
status: "cancelled",
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it("returns cancelled stripe payment intent", () => {
|
||||
|
||||
@@ -8,48 +8,78 @@ class StripeProviderService extends PaymentService {
|
||||
constructor({ customerService, totalsService, regionService }, options) {
|
||||
super()
|
||||
|
||||
/**
|
||||
* Required Stripe options:
|
||||
* {
|
||||
* api_key: "stripe_secret_key", REQUIRED
|
||||
* webhook_secret: "stripe_webhook_secret", REQUIRED
|
||||
* // Use this flag to capture payment immediately (default is false)
|
||||
* capture: true
|
||||
* }
|
||||
*/
|
||||
this.options_ = options
|
||||
|
||||
/** @private @const {Stripe} */
|
||||
this.stripe_ = Stripe(options.api_key)
|
||||
|
||||
/** @private @const {CustomerService} */
|
||||
this.customerService_ = customerService
|
||||
|
||||
/** @private @const {RegionService} */
|
||||
this.regionService_ = regionService
|
||||
|
||||
/** @private @const {TotalsService} */
|
||||
this.totalsService_ = totalsService
|
||||
}
|
||||
|
||||
/**
|
||||
* Status for Stripe PaymentIntent.
|
||||
* @param {Object} paymentData - payment method data from cart
|
||||
* Fetches Stripe payment intent. Check its status and returns the
|
||||
* corresponding Medusa status.
|
||||
* @param {object} paymentData - payment method data from cart
|
||||
* @returns {string} the status of the payment intent
|
||||
*/
|
||||
async getStatus(paymentData) {
|
||||
const { id } = paymentData
|
||||
|
||||
const paymentIntent = await this.stripe_.paymentIntents.retrieve(id)
|
||||
|
||||
let status = "initial"
|
||||
let status = "pending"
|
||||
|
||||
if (paymentIntent.status === "requires_payment_method") {
|
||||
return status
|
||||
}
|
||||
|
||||
if (paymentIntent.status === "requires_confirmation") {
|
||||
return status
|
||||
}
|
||||
|
||||
if (paymentIntent.status === "processing") {
|
||||
return status
|
||||
}
|
||||
|
||||
if (paymentIntent.status === "requires_action") {
|
||||
status = "requires_more"
|
||||
}
|
||||
|
||||
if (paymentIntent.status === "canceled") {
|
||||
status = "canceled"
|
||||
}
|
||||
|
||||
if (paymentIntent.status === "requires_capture") {
|
||||
status = "authorized"
|
||||
}
|
||||
|
||||
if (paymentIntent.status === "succeeded") {
|
||||
status = "succeeded"
|
||||
}
|
||||
|
||||
if (paymentIntent.status === "canceled") {
|
||||
status = "canceled"
|
||||
status = "authorized"
|
||||
}
|
||||
|
||||
return status
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches a customers saved payment methods if registered in Stripe.
|
||||
* @param {object} customer - customer to fetch saved cards for
|
||||
* @returns {Promise<Array<object>>} saved payments methods
|
||||
*/
|
||||
async retrieveSavedMethods(customer) {
|
||||
if (customer.metadata && customer.metadata.stripe_id) {
|
||||
const methods = await this.stripe_.paymentMethods.list({
|
||||
@@ -63,6 +93,11 @@ class StripeProviderService extends PaymentService {
|
||||
return Promise.resolve([])
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches a Stripe customer
|
||||
* @param {string} customerId - Stripe customer id
|
||||
* @returns {Promise<object>} Stripe customer
|
||||
*/
|
||||
async retrieveCustomer(customerId) {
|
||||
if (!customerId) {
|
||||
return Promise.resolve()
|
||||
@@ -70,17 +105,23 @@ class StripeProviderService extends PaymentService {
|
||||
return this.stripe_.customers.retrieve(customerId)
|
||||
}
|
||||
|
||||
// customer metadata
|
||||
/**
|
||||
* Creates a Stripe customer using a Medusa customer.
|
||||
* @param {object} customer - Customer data from Medusa
|
||||
* @returns {Promise<object>} Stripe customer
|
||||
*/
|
||||
async createCustomer(customer) {
|
||||
try {
|
||||
const stripeCustomer = await this.stripe_.customers.create({
|
||||
email: customer.email,
|
||||
})
|
||||
await this.customerService_.setMetadata(
|
||||
customer._id,
|
||||
"stripe_id",
|
||||
stripeCustomer.id
|
||||
)
|
||||
|
||||
if (customer.id) {
|
||||
await this.customerService_.update(customer.id, {
|
||||
metadata: { stripe_id: stripeCustomer.id },
|
||||
})
|
||||
}
|
||||
|
||||
return stripeCustomer
|
||||
} catch (error) {
|
||||
throw error
|
||||
@@ -88,50 +129,57 @@ class StripeProviderService extends PaymentService {
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates Stripe PaymentIntent.
|
||||
* @param {string} cart - the cart to create a payment for
|
||||
* @param {number} amount - the amount to create a payment for
|
||||
* @returns {string} id of payment intent
|
||||
* Creates a Stripe payment intent.
|
||||
* If customer is not registered in Stripe, we do so.
|
||||
* @param {object} cart - cart to create a payment for
|
||||
* @returns {object} Stripe payment intent
|
||||
*/
|
||||
async createPayment(cart) {
|
||||
const { customer_id, region_id } = cart
|
||||
const { customer_id, region_id, email } = cart
|
||||
const { currency_code } = await this.regionService_.retrieve(region_id)
|
||||
|
||||
let stripeCustomerId
|
||||
if (!customer_id) {
|
||||
const { id } = await this.stripe_.customers.create({
|
||||
email: cart.email,
|
||||
})
|
||||
stripeCustomerId = id
|
||||
} else {
|
||||
const customer = await this.customerService_.retrieve(customer_id)
|
||||
if (!(customer.metadata && customer.metadata.stripe_id)) {
|
||||
const { id } = await this.stripe_.customers.create({
|
||||
email: customer.email,
|
||||
})
|
||||
await this.customerService_.setMetadata(customer._id, "stripe_id", id)
|
||||
} else {
|
||||
stripeCustomerId = customer.metadata.stripe_id
|
||||
}
|
||||
}
|
||||
|
||||
const amount = await this.totalsService_.getTotal(cart)
|
||||
const paymentIntent = await this.stripe_.paymentIntents.create({
|
||||
customer: stripeCustomerId,
|
||||
amount: parseInt(amount * 100), // Stripe amount is in cents
|
||||
|
||||
const intentRequest = {
|
||||
amount: amount,
|
||||
currency: currency_code,
|
||||
setup_future_usage: "on_session",
|
||||
capture_method: "manual",
|
||||
metadata: { cart_id: `${cart._id}` },
|
||||
})
|
||||
capture_method: this.options_.capture ? "automatic" : "manual",
|
||||
metadata: { cart_id: `${cart.id}` },
|
||||
}
|
||||
|
||||
if (customer_id) {
|
||||
const customer = await this.customerService_.retrieve(customer_id)
|
||||
|
||||
if (customer.metadata?.stripe_id) {
|
||||
intentRequest.customer = customer.metadata.stripe_id
|
||||
} else {
|
||||
const stripeCustomer = await this.createCustomer({
|
||||
email,
|
||||
id: customer_id,
|
||||
})
|
||||
|
||||
intentRequest.customer = stripeCustomer.id
|
||||
}
|
||||
} else {
|
||||
const stripeCustomer = await this.createCustomer({
|
||||
email,
|
||||
})
|
||||
|
||||
intentRequest.customer = stripeCustomer.id
|
||||
}
|
||||
|
||||
const paymentIntent = await this.stripe_.paymentIntents.create(
|
||||
intentRequest
|
||||
)
|
||||
|
||||
return paymentIntent
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves Stripe PaymentIntent.
|
||||
* Retrieves Stripe payment intent.
|
||||
* @param {object} data - the data of the payment to retrieve
|
||||
* @returns {Object} Stripe PaymentIntent
|
||||
* @returns {Promise<object>} Stripe payment intent
|
||||
*/
|
||||
async retrievePayment(data) {
|
||||
try {
|
||||
@@ -142,26 +190,74 @@ class StripeProviderService extends PaymentService {
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates Stripe PaymentIntent.
|
||||
* @param {object} data - The payment session data.
|
||||
* @param {Object} cart - the current cart value
|
||||
* @returns {Object} Stripe PaymentIntent
|
||||
* Gets a Stripe payment intent and returns it.
|
||||
* @param {object} sessionData - the data of the payment to retrieve
|
||||
* @returns {Promise<object>} Stripe payment intent
|
||||
*/
|
||||
async updatePayment(data, cart) {
|
||||
async getPaymentData(sessionData) {
|
||||
try {
|
||||
const { id } = data
|
||||
const amount = await this.totalsService_.getTotal(cart)
|
||||
return this.stripe_.paymentIntents.update(id, {
|
||||
amount: parseInt(amount * 100),
|
||||
return this.stripe_.paymentIntents.retrieve(sessionData.data.id)
|
||||
} catch (error) {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Authorizes Stripe payment intent by simply returning
|
||||
* the status for the payment intent in use.
|
||||
* @param {object} sessionData - payment session data
|
||||
* @param {object} context - properties relevant to current context
|
||||
* @returns {Promise<{ status: string, data: object }>} result with data and status
|
||||
*/
|
||||
async authorizePayment(sessionData, context = {}) {
|
||||
const stat = await this.getStatus(sessionData.data)
|
||||
|
||||
try {
|
||||
return { data: sessionData.data, status: stat }
|
||||
} catch (error) {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
async updatePaymentData(sessionData, update) {
|
||||
try {
|
||||
return this.stripe_.paymentIntents.update(sessionData.id, {
|
||||
...update.data,
|
||||
})
|
||||
} catch (error) {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
async deletePayment(data) {
|
||||
/**
|
||||
* Updates Stripe payment intent.
|
||||
* @param {object} sessionData - payment session data.
|
||||
* @param {object} update - objec to update intent with
|
||||
* @returns {object} Stripe payment intent
|
||||
*/
|
||||
async updatePayment(sessionData, cart) {
|
||||
try {
|
||||
const { id } = data
|
||||
const stripeId = cart.customer?.metadata?.stripe_id || undefined
|
||||
|
||||
if (stripeId !== sessionData.customer) {
|
||||
return this.createPayment(cart)
|
||||
} else {
|
||||
if (cart.total && sessionData.amount === cart.total) {
|
||||
return sessionData
|
||||
}
|
||||
|
||||
return this.stripe_.paymentIntents.update(sessionData.id, {
|
||||
amount: cart.total,
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
async deletePayment(payment) {
|
||||
try {
|
||||
const { id } = payment.data
|
||||
return this.stripe_.paymentIntents.cancel(id).catch((err) => {
|
||||
if (err.statusCode === 400) {
|
||||
return
|
||||
@@ -174,15 +270,15 @@ class StripeProviderService extends PaymentService {
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates customer of Stripe PaymentIntent.
|
||||
* @param {string} cart - the cart to update payment intent for
|
||||
* @param {Object} data - the update object for the payment intent
|
||||
* @returns {Object} Stripe PaymentIntent
|
||||
* Updates customer of Stripe payment intent.
|
||||
* @param {string} paymentIntentId - id of payment intent to update
|
||||
* @param {string} customerId - id of new Stripe customer
|
||||
* @returns {object} Stripe payment intent
|
||||
*/
|
||||
async updatePaymentIntentCustomer(paymentIntent, id) {
|
||||
async updatePaymentIntentCustomer(paymentIntentId, customerId) {
|
||||
try {
|
||||
return this.stripe_.paymentIntents.update(paymentIntent, {
|
||||
customer: id,
|
||||
return this.stripe_.paymentIntents.update(paymentIntentId, {
|
||||
customer: customerId,
|
||||
})
|
||||
} catch (error) {
|
||||
throw error
|
||||
@@ -190,12 +286,12 @@ class StripeProviderService extends PaymentService {
|
||||
}
|
||||
|
||||
/**
|
||||
* Captures payment for Stripe PaymentIntent.
|
||||
* @param {Object} paymentData - payment method data from cart
|
||||
* @returns {Object} Stripe PaymentIntent
|
||||
* Captures payment for Stripe payment intent.
|
||||
* @param {object} paymentData - payment method data from cart
|
||||
* @returns {object} Stripe payment intent
|
||||
*/
|
||||
async capturePayment(paymentData) {
|
||||
const { id } = paymentData
|
||||
async capturePayment(payment) {
|
||||
const { id } = payment.data
|
||||
try {
|
||||
return this.stripe_.paymentIntents.capture(id)
|
||||
} catch (error) {
|
||||
@@ -204,32 +300,34 @@ class StripeProviderService extends PaymentService {
|
||||
}
|
||||
|
||||
/**
|
||||
* Refunds payment for Stripe PaymentIntent.
|
||||
* @param {Object} paymentData - payment method data from cart
|
||||
* @returns {string} id of payment intent
|
||||
* Refunds payment for Stripe payment intent.
|
||||
* @param {object} paymentData - payment method data from cart
|
||||
* @param {number} amountToRefund - amount to refund
|
||||
* @returns {string} refunded payment intent
|
||||
*/
|
||||
async refundPayment(paymentData, amount) {
|
||||
const { id } = paymentData
|
||||
async refundPayment(payment, amountToRefund) {
|
||||
const { id } = payment.data
|
||||
try {
|
||||
return this.stripe_.refunds.create({
|
||||
amount: parseInt(amount * 100),
|
||||
await this.stripe_.refunds.create({
|
||||
amount: amountToRefund,
|
||||
payment_intent: id,
|
||||
})
|
||||
|
||||
return payment.data
|
||||
} catch (error) {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancels payment for Stripe PaymentIntent.
|
||||
* @param {Object} paymentData - payment method data from cart
|
||||
* @returns {string} id of payment intent
|
||||
* Cancels payment for Stripe payment intent.
|
||||
* @param {object} paymentData - payment method data from cart
|
||||
* @returns {object} canceled payment intent
|
||||
*/
|
||||
async cancelPayment(paymentData) {
|
||||
const { id } = paymentData
|
||||
async cancelPayment(payment) {
|
||||
const { id } = payment.data
|
||||
try {
|
||||
const result = await this.stripe_.paymentIntents.cancel(id)
|
||||
return result
|
||||
return this.stripe_.paymentIntents.cancel(id)
|
||||
} catch (error) {
|
||||
if (error.payment_intent.status === "canceled") {
|
||||
return error.payment_intent
|
||||
@@ -241,10 +339,10 @@ class StripeProviderService extends PaymentService {
|
||||
|
||||
/**
|
||||
* Constructs Stripe Webhook event
|
||||
* @param {Object} data - the data of the webhook request: req.body
|
||||
* @param {Object} signature - the Stripe signature on the event, that
|
||||
* @param {object} data - the data of the webhook request: req.body
|
||||
* @param {object} signature - the Stripe signature on the event, that
|
||||
* ensures integrity of the webhook event
|
||||
* @returns {Object} Stripe Webhook event
|
||||
* @returns {object} Stripe Webhook event
|
||||
*/
|
||||
constructWebhookEvent(data, signature) {
|
||||
return this.stripe_.webhooks.constructEvent(
|
||||
|
||||
@@ -1,93 +0,0 @@
|
||||
import { IdMap } from "medusa-test-utils"
|
||||
import { carts, CartServiceMock } from "../../__mocks__/cart"
|
||||
import { CustomerServiceMock } from "../../__mocks__/customer"
|
||||
import { StripeProviderServiceMock } from "../../services/__mocks__/stripe-provider"
|
||||
import { EventBusServiceMock } from "../../__mocks__/eventbus"
|
||||
import CartSubscriber from "../cart"
|
||||
|
||||
describe("CartSubscriber", () => {
|
||||
describe("onCustomerUpdated", () => {
|
||||
let cartSubcriber = new CartSubscriber({
|
||||
eventBusService: EventBusServiceMock,
|
||||
cartService: CartServiceMock,
|
||||
stripeProviderService: StripeProviderServiceMock,
|
||||
customerService: CustomerServiceMock,
|
||||
})
|
||||
|
||||
beforeEach(async () => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
it("resolves on non-existing payment data", async () => {
|
||||
await cartSubcriber.onCustomerUpdated(carts.emptyCart)
|
||||
|
||||
expect(CustomerServiceMock.retrieve).toHaveBeenCalledTimes(0)
|
||||
})
|
||||
|
||||
it("cancels old and creates new payment intent with the updated existing customer", async () => {
|
||||
await cartSubcriber.onCustomerUpdated(carts.frCart)
|
||||
|
||||
expect(CustomerServiceMock.retrieve).toHaveBeenCalledTimes(1)
|
||||
expect(CustomerServiceMock.retrieve).toHaveBeenCalledWith(
|
||||
IdMap.getId("lebron")
|
||||
)
|
||||
|
||||
expect(StripeProviderServiceMock.retrievePayment).toHaveBeenCalledTimes(1)
|
||||
expect(StripeProviderServiceMock.retrievePayment).toHaveBeenCalledWith(
|
||||
carts.frCart.payment_sessions[0].data
|
||||
)
|
||||
|
||||
expect(StripeProviderServiceMock.cancelPayment).toHaveBeenCalledTimes(1)
|
||||
expect(StripeProviderServiceMock.cancelPayment).toHaveBeenCalledWith({
|
||||
id: "pi",
|
||||
customer: "cus_123456789",
|
||||
})
|
||||
|
||||
expect(StripeProviderServiceMock.createPayment).toHaveBeenCalledTimes(1)
|
||||
expect(StripeProviderServiceMock.createPayment).toHaveBeenCalledWith(
|
||||
carts.frCart
|
||||
)
|
||||
|
||||
expect(CartServiceMock.updatePaymentSession).toHaveBeenCalledTimes(1)
|
||||
expect(CartServiceMock.updatePaymentSession).toHaveBeenCalledWith(
|
||||
IdMap.getId("fr-cart"),
|
||||
"stripe",
|
||||
{
|
||||
id: "pi_new",
|
||||
customer: "cus_123456789_new",
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
it("cancels old and creates new payment intent and creates new stripe customer", async () => {
|
||||
await cartSubcriber.onCustomerUpdated(carts.frCartNoStripeCustomer)
|
||||
|
||||
expect(CustomerServiceMock.retrieve).toHaveBeenCalledTimes(1)
|
||||
expect(CustomerServiceMock.retrieve).toHaveBeenCalledWith(
|
||||
IdMap.getId("vvd")
|
||||
)
|
||||
|
||||
expect(StripeProviderServiceMock.retrievePayment).toHaveBeenCalledTimes(1)
|
||||
expect(StripeProviderServiceMock.retrievePayment).toHaveBeenCalledWith(
|
||||
carts.frCartNoStripeCustomer.payment_sessions[0].data
|
||||
)
|
||||
|
||||
expect(StripeProviderServiceMock.createCustomer).toHaveBeenCalledTimes(1)
|
||||
expect(StripeProviderServiceMock.createCustomer).toHaveBeenCalledWith({
|
||||
_id: IdMap.getId("vvd"),
|
||||
first_name: "Virgil",
|
||||
last_name: "Van Dijk",
|
||||
email: "virg@vvd.com",
|
||||
password_hash: "1234",
|
||||
metadata: {},
|
||||
})
|
||||
|
||||
expect(
|
||||
StripeProviderServiceMock.updatePaymentIntentCustomer
|
||||
).toHaveBeenCalledTimes(1)
|
||||
expect(
|
||||
StripeProviderServiceMock.updatePaymentIntentCustomer
|
||||
).toHaveBeenCalledWith("cus_123456789_new_vvd")
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -2,12 +2,12 @@ class CartSubscriber {
|
||||
constructor({
|
||||
cartService,
|
||||
customerService,
|
||||
stripeProviderService,
|
||||
paymentProviderService,
|
||||
eventBusService,
|
||||
}) {
|
||||
this.cartService_ = cartService
|
||||
this.customerService_ = customerService
|
||||
this.stripeProviderService_ = stripeProviderService
|
||||
this.paymentProviderService_ = paymentProviderService
|
||||
this.eventBus_ = eventBusService
|
||||
|
||||
this.eventBus_.subscribe("cart.customer_updated", async (cart) => {
|
||||
@@ -15,64 +15,37 @@ class CartSubscriber {
|
||||
})
|
||||
}
|
||||
|
||||
async onCustomerUpdated(cart) {
|
||||
const { customer_id, payment_sessions } = cart
|
||||
async onCustomerUpdated(cartId) {
|
||||
const cart = await this.cartService_.retrieve(cartId, {
|
||||
select: [
|
||||
"subtotal",
|
||||
"tax_total",
|
||||
"shipping_total",
|
||||
"discount_total",
|
||||
"total",
|
||||
],
|
||||
relations: [
|
||||
"items",
|
||||
"billing_address",
|
||||
"shipping_address",
|
||||
"region",
|
||||
"region.payment_providers",
|
||||
"payment_sessions",
|
||||
"customer",
|
||||
],
|
||||
})
|
||||
|
||||
if (!payment_sessions) {
|
||||
if (!cart.payment_sessions?.length) {
|
||||
return Promise.resolve()
|
||||
}
|
||||
|
||||
const customer = await this.customerService_.retrieve(customer_id)
|
||||
|
||||
const stripeSession = payment_sessions.find(
|
||||
(s) => s.provider_id === "stripe"
|
||||
const session = cart.payment_sessions.find(
|
||||
(ps) => ps.provider_id === "stripe"
|
||||
)
|
||||
|
||||
if (!stripeSession) {
|
||||
return Promise.resolve()
|
||||
if (session) {
|
||||
return this.paymentProviderService_.updateSession(session, cart)
|
||||
}
|
||||
|
||||
const paymentIntent = await this.stripeProviderService_.retrievePayment(
|
||||
stripeSession.data
|
||||
)
|
||||
|
||||
let stripeCustomer
|
||||
if (customer.metadata && customer.metadata.stripe_id) {
|
||||
stripeCustomer = await this.stripeProviderService_.retrieveCustomer(
|
||||
customer.metadata.stripe_id
|
||||
)
|
||||
}
|
||||
|
||||
if (!stripeCustomer) {
|
||||
stripeCustomer = await this.stripeProviderService_.createCustomer(
|
||||
customer
|
||||
)
|
||||
}
|
||||
|
||||
if (stripeCustomer.id === paymentIntent.customer) {
|
||||
return Promise.resolve()
|
||||
}
|
||||
|
||||
if (!paymentIntent.customer) {
|
||||
return this.stripeProviderService_.updatePaymentIntentCustomer(
|
||||
stripeCustomer.id
|
||||
)
|
||||
}
|
||||
|
||||
if (stripeCustomer.id !== paymentIntent.customer) {
|
||||
await this.stripeProviderService_.cancelPayment(paymentIntent)
|
||||
const newPaymentIntent = await this.stripeProviderService_.createPayment(
|
||||
cart
|
||||
)
|
||||
|
||||
await this.cartService_.updatePaymentSession(
|
||||
cart._id,
|
||||
"stripe",
|
||||
newPaymentIntent
|
||||
)
|
||||
}
|
||||
|
||||
return Promise.resolve()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "medusa-plugin-add-ons",
|
||||
"version": "1.0.8",
|
||||
"version": "1.0.7-alpha.176+0646bd3",
|
||||
"description": "Add-on plugin for Medusa Commerce",
|
||||
"main": "index.js",
|
||||
"repository": {
|
||||
@@ -25,7 +25,7 @@
|
||||
"cross-env": "^7.0.2",
|
||||
"eslint": "^6.8.0",
|
||||
"jest": "^25.5.2",
|
||||
"medusa-test-utils": "^1.0.13"
|
||||
"medusa-test-utils": "^1.0.12-alpha.176+0646bd3"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "babel src -d . --ignore **/__tests__",
|
||||
@@ -37,8 +37,8 @@
|
||||
"body-parser": "^1.19.0",
|
||||
"cors": "^2.8.5",
|
||||
"express": "^4.17.1",
|
||||
"medusa-core-utils": "^1.0.11",
|
||||
"medusa-core-utils": "^1.0.12-alpha.787+0646bd3",
|
||||
"redis": "^3.0.2"
|
||||
},
|
||||
"gitHead": "3bd91f65304ed1d31c41b85d5c87123450e0542e"
|
||||
"gitHead": "0646bd395a6056657cb0aa93c13699c4a9dbbcdd"
|
||||
}
|
||||
@@ -39,16 +39,7 @@ class AddOnService extends BaseService {
|
||||
* @return {string} the validated id
|
||||
*/
|
||||
validateId_(rawId) {
|
||||
const schema = Validator.objectId()
|
||||
const { value, error } = schema.validate(rawId.toString())
|
||||
if (error) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_ARGUMENT,
|
||||
"The addOnId could not be casted to an ObjectId"
|
||||
)
|
||||
}
|
||||
|
||||
return value
|
||||
return rawId
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -187,7 +178,7 @@ class AddOnService extends BaseService {
|
||||
})
|
||||
|
||||
// Return the price if we found a suitable match
|
||||
if (price) {
|
||||
if (price !== undefined) {
|
||||
return price
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "medusa-plugin-brightpearl",
|
||||
"version": "1.0.26",
|
||||
"version": "1.0.21-alpha.176+0646bd3",
|
||||
"description": "Brightpearl plugin for Medusa Commerce",
|
||||
"main": "index.js",
|
||||
"repository": {
|
||||
@@ -27,7 +27,7 @@
|
||||
"cross-env": "^7.0.2",
|
||||
"eslint": "^6.8.0",
|
||||
"jest": "^25.5.2",
|
||||
"medusa-test-utils": "^1.0.13",
|
||||
"medusa-test-utils": "^1.0.12-alpha.176+0646bd3",
|
||||
"prettier": "^2.0.5"
|
||||
},
|
||||
"scripts": {
|
||||
@@ -43,8 +43,8 @@
|
||||
"axios": "^0.19.2",
|
||||
"axios-rate-limit": "^1.2.1",
|
||||
"express": "^4.17.1",
|
||||
"medusa-core-utils": "^1.0.11",
|
||||
"medusa-core-utils": "^1.0.12-alpha.787+0646bd3",
|
||||
"randomatic": "^3.1.1"
|
||||
},
|
||||
"gitHead": "3bd91f65304ed1d31c41b85d5c87123450e0542e"
|
||||
"gitHead": "0646bd395a6056657cb0aa93c13699c4a9dbbcdd"
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
const REFRESH_CRON = process.env.BP_REFRESH_CRON || "5 4 * * */6"
|
||||
|
||||
const refreshToken = async (container) => {
|
||||
const logger = container.resolve("logger")
|
||||
const oauthService = container.resolve("oauthService")
|
||||
const eventBus = container.resolve("eventBusService")
|
||||
|
||||
try {
|
||||
logger.info("registering refresh cron job BP")
|
||||
eventBus.createCronJob("refresh-token-bp", {}, REFRESH_CRON, async () => {
|
||||
const appData = await oauthService.retrieveByName("brightpearl")
|
||||
const data = appData.data
|
||||
if (data && data.refresh_token) {
|
||||
return oauthService.refreshToken("brightpearl", data.refresh_token)
|
||||
}
|
||||
})
|
||||
} catch (err) {
|
||||
if (err.name === "not_allowed") {
|
||||
return
|
||||
}
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
export default refreshToken
|
||||
@@ -6,8 +6,52 @@ import MockAdapter from "axios-mock-adapter"
|
||||
|
||||
jest.mock("../../utils/brightpearl")
|
||||
|
||||
const order = {
|
||||
region: {
|
||||
tax_code: "1234",
|
||||
},
|
||||
items: [
|
||||
{
|
||||
title: "Test",
|
||||
variant: {
|
||||
sku: "TEST",
|
||||
},
|
||||
unit_price: 1100,
|
||||
quantity: 2,
|
||||
},
|
||||
],
|
||||
shipping_total: 12399,
|
||||
shipping_methods: [
|
||||
{
|
||||
name: "standard",
|
||||
price: 12399,
|
||||
},
|
||||
],
|
||||
payment_method: {
|
||||
id: "123",
|
||||
},
|
||||
tax_rate: 23.1,
|
||||
currency_code: "DKK",
|
||||
display_id: "1234",
|
||||
id: "12355",
|
||||
discounts: [],
|
||||
shipping_address: {
|
||||
first_name: "Test",
|
||||
last_name: "Testson",
|
||||
address_1: "Test",
|
||||
address_2: "TEst",
|
||||
postal_code: "1234",
|
||||
country_code: "DK",
|
||||
phone: "12345678",
|
||||
},
|
||||
email: "test@example.com",
|
||||
}
|
||||
|
||||
const OrderService = {
|
||||
setMetadata: () => {
|
||||
retrieve: () => {
|
||||
return Promise.resolve(order)
|
||||
},
|
||||
update: () => {
|
||||
return Promise.resolve()
|
||||
},
|
||||
}
|
||||
@@ -20,7 +64,7 @@ const TotalsService = {
|
||||
return Promise.resolve([])
|
||||
},
|
||||
getShippingTotal: () => {
|
||||
return 123.9999929393293
|
||||
return 12399
|
||||
},
|
||||
rounded: (value) => {
|
||||
const decimalPlaces = 4
|
||||
@@ -91,45 +135,6 @@ describe("BrightpearlService", () => {
|
||||
})
|
||||
|
||||
describe("createSalesOrder", () => {
|
||||
const order = {
|
||||
items: [
|
||||
{
|
||||
title: "Test",
|
||||
content: {
|
||||
variant: {
|
||||
sku: "TEST",
|
||||
},
|
||||
unit_price: 11,
|
||||
},
|
||||
quantity: 2,
|
||||
},
|
||||
],
|
||||
shipping_methods: [
|
||||
{
|
||||
name: "standard",
|
||||
price: 123.9999929393293,
|
||||
},
|
||||
],
|
||||
payment_method: {
|
||||
_id: "123",
|
||||
},
|
||||
tax_rate: 0.231,
|
||||
currency_code: "DKK",
|
||||
display_id: "1234",
|
||||
_id: "12355",
|
||||
discounts: [],
|
||||
shipping_address: {
|
||||
first_name: "Test",
|
||||
last_name: "Testson",
|
||||
address_1: "Test",
|
||||
address_2: "TEst",
|
||||
postal_code: "1234",
|
||||
country_code: "DK",
|
||||
phone: "12345678",
|
||||
},
|
||||
email: "test@example.com",
|
||||
}
|
||||
|
||||
const bpService = new BrightpearlService(
|
||||
{
|
||||
orderService: OrderService,
|
||||
@@ -187,8 +192,8 @@ describe("BrightpearlService", () => {
|
||||
{
|
||||
name: "Shipping: standard",
|
||||
quantity: 1,
|
||||
net: 124,
|
||||
tax: 28.644,
|
||||
net: 123.99,
|
||||
tax: 28.6417,
|
||||
taxCode: "1234",
|
||||
nominalCode: "4040",
|
||||
},
|
||||
|
||||
@@ -160,7 +160,7 @@ class BrightpearlService extends BaseService {
|
||||
|
||||
// Only update if the inventory levels have changed
|
||||
if (parseInt(v.inventory_quantity) !== parseInt(onHand)) {
|
||||
return this.productVariantService_.update(v._id, {
|
||||
return this.productVariantService_.update(v.id, {
|
||||
inventory_quantity: onHand,
|
||||
})
|
||||
}
|
||||
@@ -181,10 +181,13 @@ class BrightpearlService extends BaseService {
|
||||
const onHand = availability[productId].total.onHand
|
||||
|
||||
const sku = brightpearlProduct.identity.sku
|
||||
const [variant] = await this.productVariantService_.list({ sku })
|
||||
if (!sku) return
|
||||
|
||||
const variant = await this.productVariantService_
|
||||
.retrieveBySKU(sku)
|
||||
.catch((_) => undefined)
|
||||
if (variant && variant.manage_inventory) {
|
||||
await this.productVariantService_.update(variant._id, {
|
||||
await this.productVariantService_.update(variant.id, {
|
||||
inventory_quantity: onHand,
|
||||
})
|
||||
}
|
||||
@@ -249,7 +252,7 @@ class BrightpearlService extends BaseService {
|
||||
}
|
||||
|
||||
async createRefundCredit(fromOrder, fromRefund) {
|
||||
const region = await this.regionService_.retrieve(fromOrder.region_id)
|
||||
const region = fromOrder.region
|
||||
const client = await this.getClient()
|
||||
const authData = await this.getAuthData()
|
||||
const orderId = fromOrder.metadata.brightpearl_sales_order_id
|
||||
@@ -266,7 +269,7 @@ class BrightpearlService extends BaseService {
|
||||
const order = {
|
||||
currency: parentSo.currency,
|
||||
ref: parentSo.ref,
|
||||
externalRef: `${parentSo.externalRef}.${fromOrder.refunds.length}`,
|
||||
externalRef: `${parentSo.externalRef}.${fromRefund.id}`,
|
||||
channelId: this.options.channel_id || `1`,
|
||||
installedIntegrationInstanceId: authData.installation_instance_id,
|
||||
customer: parentSo.customer,
|
||||
@@ -277,11 +280,12 @@ class BrightpearlService extends BaseService {
|
||||
name: `${fromRefund.reason}: ${fromRefund.note}`,
|
||||
quantity: 1,
|
||||
taxCode: region.tax_code,
|
||||
net: this.totalsService_.rounded(
|
||||
fromRefund.amount / (1 + fromOrder.tax_rate)
|
||||
net: this.bpnum_(
|
||||
fromRefund.amount,
|
||||
10000 / (100 + fromOrder.tax_rate)
|
||||
),
|
||||
tax: this.totalsService_.rounded(
|
||||
fromRefund.amount - fromRefund.amount / (1 + fromOrder.tax_rate)
|
||||
tax: this.bpnum_(
|
||||
fromRefund.amount * (1 - 100 / (100 + fromOrder.tax_rate))
|
||||
),
|
||||
nominalCode: accountingCode,
|
||||
},
|
||||
@@ -291,15 +295,15 @@ class BrightpearlService extends BaseService {
|
||||
return client.orders
|
||||
.createCredit(order)
|
||||
.then(async (creditId) => {
|
||||
const paymentMethod = fromOrder.payment_method
|
||||
const paymentMethod = fromOrder.payments[0]
|
||||
const paymentType = "PAYMENT"
|
||||
const payment = {
|
||||
transactionRef: `${paymentMethod._id}.${fromOrder.refunds.length}`,
|
||||
transactionCode: fromOrder._id,
|
||||
transactionRef: `${paymentMethod.id}.${fromRefund.id}`,
|
||||
transactionCode: fromOrder.id,
|
||||
paymentMethodCode: this.options.payment_method_code || "1220",
|
||||
orderId: creditId,
|
||||
currencyIsoCode: fromOrder.currency_code,
|
||||
amountPaid: fromRefund.amount,
|
||||
currencyIsoCode: fromOrder.currency_code.toUpperCase(),
|
||||
amountPaid: this.bpnum_(fromRefund.amount),
|
||||
paymentDate: new Date(),
|
||||
paymentType,
|
||||
}
|
||||
@@ -309,18 +313,21 @@ class BrightpearlService extends BaseService {
|
||||
|
||||
await client.payments.create(payment)
|
||||
|
||||
return this.orderService_.setMetadata(
|
||||
fromOrder._id,
|
||||
"brightpearl_credit_ids",
|
||||
newIds
|
||||
)
|
||||
return this.orderService_.update(fromOrder.id, {
|
||||
metadata: {
|
||||
brightpearl_credit_ids: newIds,
|
||||
},
|
||||
})
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log(err)
|
||||
console.log(err.response.data.errors)
|
||||
})
|
||||
.catch((err) => console.log(err.response.data.errors))
|
||||
}
|
||||
}
|
||||
|
||||
async createSalesCredit(fromOrder, fromReturn) {
|
||||
const region = await this.regionService_.retrieve(fromOrder.region_id)
|
||||
const region = fromOrder.region
|
||||
const client = await this.getClient()
|
||||
const authData = await this.getAuthData()
|
||||
const orderId = fromOrder.metadata.brightpearl_sales_order_id
|
||||
@@ -329,7 +336,7 @@ class BrightpearlService extends BaseService {
|
||||
const order = {
|
||||
currency: parentSo.currency,
|
||||
ref: parentSo.ref,
|
||||
externalRef: `${parentSo.externalRef}.${fromOrder.refunds.length}`,
|
||||
externalRef: `${parentSo.externalRef}.${fromReturn.id}`,
|
||||
channelId: this.options.channel_id || `1`,
|
||||
installedIntegrationInstanceId: authData.installation_instance_id,
|
||||
customer: parentSo.customer,
|
||||
@@ -359,18 +366,14 @@ class BrightpearlService extends BaseService {
|
||||
return acc + next.net + next.tax
|
||||
}, 0)
|
||||
|
||||
const difference = fromReturn.refund_amount - total
|
||||
const difference = (fromReturn.refund_amount / 100 - total) * 100
|
||||
if (difference) {
|
||||
order.rows.push({
|
||||
name: "Difference",
|
||||
quantity: 1,
|
||||
taxCode: region.tax_code,
|
||||
net: this.totalsService_.rounded(
|
||||
difference / (1 + fromOrder.tax_rate)
|
||||
),
|
||||
tax: this.totalsService_.rounded(
|
||||
difference - difference / (1 + fromOrder.tax_rate)
|
||||
),
|
||||
net: this.bpnum_(difference, 10000 / (100 + fromOrder.tax_rate)),
|
||||
tax: this.bpnum_(difference * (1 - 100 / (100 + fromOrder.tax_rate))),
|
||||
nominalCode: this.options.sales_account_code || "4000",
|
||||
})
|
||||
}
|
||||
@@ -378,15 +381,15 @@ class BrightpearlService extends BaseService {
|
||||
return client.orders
|
||||
.createCredit(order)
|
||||
.then(async (creditId) => {
|
||||
const paymentMethod = fromOrder.payment_method
|
||||
const paymentMethod = fromOrder.payments[0]
|
||||
const paymentType = "PAYMENT"
|
||||
const payment = {
|
||||
transactionRef: `${paymentMethod._id}.${fromOrder.refunds.length}`,
|
||||
transactionCode: fromOrder._id,
|
||||
transactionRef: `${paymentMethod.id}.${fromReturn.id}`,
|
||||
transactionCode: fromOrder.id,
|
||||
paymentMethodCode: this.options.payment_method_code || "1220",
|
||||
orderId: creditId,
|
||||
currencyIsoCode: fromOrder.currency_code,
|
||||
amountPaid: fromReturn.refund_amount,
|
||||
currencyIsoCode: fromOrder.currency_code.toUpperCase(),
|
||||
amountPaid: this.bpnum_(fromReturn.refund_amount),
|
||||
paymentDate: new Date(),
|
||||
paymentType,
|
||||
}
|
||||
@@ -396,17 +399,34 @@ class BrightpearlService extends BaseService {
|
||||
|
||||
await client.payments.create(payment)
|
||||
|
||||
return this.orderService_.setMetadata(
|
||||
fromOrder._id,
|
||||
"brightpearl_credit_ids",
|
||||
newIds
|
||||
)
|
||||
return this.orderService_.update(fromOrder.id, {
|
||||
metadata: {
|
||||
brightpearl_credit_ids: newIds,
|
||||
},
|
||||
})
|
||||
})
|
||||
.catch((err) => console.log(err.response.data.errors))
|
||||
.catch((err) => console.log(err))
|
||||
}
|
||||
}
|
||||
|
||||
async createSalesOrder(fromOrder) {
|
||||
async createSalesOrder(fromOrderId) {
|
||||
const fromOrder = await this.orderService_.retrieve(fromOrderId, {
|
||||
select: [
|
||||
"total",
|
||||
"subtotal",
|
||||
"shipping_total",
|
||||
"gift_card_total",
|
||||
"discount_total",
|
||||
],
|
||||
relations: [
|
||||
"region",
|
||||
"shipping_address",
|
||||
"billing_address",
|
||||
"shipping_methods",
|
||||
"payments",
|
||||
],
|
||||
})
|
||||
|
||||
const client = await this.getClient()
|
||||
let customer = await this.retrieveCustomerByEmail(fromOrder.email)
|
||||
|
||||
@@ -420,10 +440,10 @@ class BrightpearlService extends BaseService {
|
||||
const { shipping_address } = fromOrder
|
||||
const order = {
|
||||
currency: {
|
||||
code: fromOrder.currency_code,
|
||||
code: fromOrder.currency_code.toUpperCase(),
|
||||
},
|
||||
ref: fromOrder.display_id,
|
||||
externalRef: fromOrder._id,
|
||||
externalRef: fromOrder.id,
|
||||
channelId: this.options.channel_id || `1`,
|
||||
installedIntegrationInstanceId: authData.installation_instance_id,
|
||||
statusId: this.options.default_status_id || `3`,
|
||||
@@ -464,33 +484,34 @@ class BrightpearlService extends BaseService {
|
||||
return salesOrderId
|
||||
})
|
||||
.then((salesOrderId) => {
|
||||
return this.orderService_.setMetadata(
|
||||
fromOrder._id,
|
||||
"brightpearl_sales_order_id",
|
||||
salesOrderId
|
||||
)
|
||||
return this.orderService_.update(fromOrder.id, {
|
||||
metadata: {
|
||||
brightpearl_sales_order_id: salesOrderId,
|
||||
},
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
async createSwapPayment(fromSwap) {
|
||||
async createSwapPayment(fromSwapId) {
|
||||
const fromSwap = await this.swapService_.retrieve(fromSwapId)
|
||||
|
||||
const client = await this.getClient()
|
||||
const soId =
|
||||
fromSwap.metadata && fromSwap.metadata.brightpearl_sales_order_id
|
||||
|
||||
if (!soId || fromSwap.amount_paid <= 0) {
|
||||
if (!soId || fromSwap.difference_due <= 0) {
|
||||
return
|
||||
}
|
||||
|
||||
const paymentType = "RECEIPT"
|
||||
const paymentMethod = fromSwap.payment_method
|
||||
const payment = {
|
||||
transactionRef: `${paymentMethod._id}.${paymentType}`, // Brightpearl cannot accept an auth and capture with same ref
|
||||
transactionCode: fromSwap._id,
|
||||
transactionRef: `${fromSwap.id}.${paymentType}`, // Brightpearl cannot accept an auth and capture with same ref
|
||||
transactionCode: fromSwap.id,
|
||||
paymentMethodCode: this.options.payment_method_code || "1220",
|
||||
orderId: soId,
|
||||
paymentDate: new Date(),
|
||||
currencyIsoCode: fromSwap.currency_code,
|
||||
amountPaid: fromSwap.amount_paid,
|
||||
currencyIsoCode: fromSwap.order.currency_code.toUpperCase(),
|
||||
amountPaid: fromSwap.difference_due,
|
||||
paymentType,
|
||||
}
|
||||
|
||||
@@ -502,20 +523,23 @@ class BrightpearlService extends BaseService {
|
||||
let customer = await this.retrieveCustomerByEmail(fromOrder.email)
|
||||
|
||||
if (!customer) {
|
||||
customer = await this.createCustomer(fromOrder)
|
||||
customer = await this.createCustomer({
|
||||
...fromOrder,
|
||||
...fromSwap,
|
||||
})
|
||||
}
|
||||
|
||||
const authData = await this.getAuthData()
|
||||
|
||||
const sIndex = fromOrder.swaps.findIndex((s) => fromSwap._id.equals(s))
|
||||
const sIndex = fromOrder.swaps.findIndex((s) => fromSwap.id === s.id)
|
||||
|
||||
const { shipping_address } = fromSwap
|
||||
const order = {
|
||||
currency: {
|
||||
code: fromOrder.currency_code,
|
||||
code: fromOrder.currency_code.toUpperCase(),
|
||||
},
|
||||
ref: `${fromOrder.display_id}-S${sIndex + 1}`,
|
||||
externalRef: `${fromOrder._id}.${fromSwap._id}`,
|
||||
externalRef: `${fromOrder.id}.${fromSwap.id}`,
|
||||
channelId: this.options.channel_id || `1`,
|
||||
installedIntegrationInstanceId: authData.installation_instance_id,
|
||||
statusId:
|
||||
@@ -545,7 +569,7 @@ class BrightpearlService extends BaseService {
|
||||
},
|
||||
},
|
||||
rows: await this.getBrightpearlRows({
|
||||
region_id: fromOrder.region_id,
|
||||
region: fromOrder.region,
|
||||
discounts: fromOrder.discounts,
|
||||
tax_rate: fromOrder.tax_rate,
|
||||
items: fromSwap.additional_items,
|
||||
@@ -562,14 +586,14 @@ class BrightpearlService extends BaseService {
|
||||
return acc + parseFloat(next.net) + parseFloat(next.tax)
|
||||
}, 0)
|
||||
|
||||
const paymentMethod = fromOrder.payment_method
|
||||
const paymentMethod = fromOrder.payments[0]
|
||||
const paymentType = "RECEIPT"
|
||||
const payment = {
|
||||
transactionRef: `${paymentMethod._id}.${paymentType}-${fromSwap._id}`,
|
||||
transactionCode: fromOrder._id,
|
||||
transactionRef: `${paymentMethod.id}.${paymentType}-${fromSwap.id}`,
|
||||
transactionCode: fromOrder.id,
|
||||
paymentMethodCode: this.options.payment_method_code || "1220",
|
||||
orderId: salesOrderId,
|
||||
currencyIsoCode: fromOrder.currency_code,
|
||||
currencyIsoCode: fromOrder.currency_code.toUpperCase(),
|
||||
amountPaid: total,
|
||||
paymentDate: new Date(),
|
||||
paymentType,
|
||||
@@ -577,33 +601,33 @@ class BrightpearlService extends BaseService {
|
||||
|
||||
await client.payments.create(payment)
|
||||
|
||||
return this.swapService_.setMetadata(
|
||||
fromSwap._id,
|
||||
"brightpearl_sales_order_id",
|
||||
salesOrderId
|
||||
)
|
||||
return this.swapService_.update(fromSwap.id, {
|
||||
metadata: {
|
||||
brightpearl_sales_order_id: salesOrderId,
|
||||
},
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
async createSwapCredit(fromOrder, fromSwap) {
|
||||
const region = await this.regionService_.retrieve(fromOrder.region_id)
|
||||
const region = fromOrder.region
|
||||
const client = await this.getClient()
|
||||
const authData = await this.getAuthData()
|
||||
const orderId = fromOrder.metadata.brightpearl_sales_order_id
|
||||
const sIndex = fromOrder.swaps.findIndex((s) => fromSwap._id.equals(s))
|
||||
const sIndex = fromOrder.swaps.findIndex((s) => fromSwap.id === s.id)
|
||||
|
||||
if (orderId) {
|
||||
const parentSo = await client.orders.retrieve(orderId)
|
||||
const order = {
|
||||
currency: parentSo.currency,
|
||||
ref: `${parentSo.ref}-S${sIndex + 1}`,
|
||||
externalRef: `${parentSo.externalRef}.${fromSwap._id}`,
|
||||
externalRef: `${parentSo.externalRef}.${fromSwap.id}`,
|
||||
channelId: this.options.channel_id || `1`,
|
||||
installedIntegrationInstanceId: authData.installation_instance_id,
|
||||
customer: parentSo.customer,
|
||||
delivery: parentSo.delivery,
|
||||
parentId: orderId,
|
||||
rows: fromSwap.return.items.map((i) => {
|
||||
rows: fromSwap.return_order.items.map((i) => {
|
||||
const parentRow = parentSo.rows.find((row) => {
|
||||
return row.externalRef === i.item_id
|
||||
})
|
||||
@@ -623,13 +647,18 @@ class BrightpearlService extends BaseService {
|
||||
}),
|
||||
}
|
||||
|
||||
if (fromSwap.return_shipping && fromSwap.return_shipping.price) {
|
||||
if (
|
||||
fromSwap.return_order.shipping_method &&
|
||||
fromSwap.return_order.shipping_method.price
|
||||
) {
|
||||
order.rows.push({
|
||||
name: "Return Shipping",
|
||||
quantity: 1,
|
||||
taxCode: region.tax_code,
|
||||
net: -1 * fromSwap.return_shipping.price,
|
||||
tax: -1 * fromSwap.return_shipping.price * fromOrder.tax_rate,
|
||||
net: (-1 * fromSwap.return_order.shipping_method.price) / 100,
|
||||
tax:
|
||||
((-1 * fromSwap.return_order.shipping_method.price) / 100) *
|
||||
(fromOrder.tax_rate / 100),
|
||||
nominalCode: this.options.shipping_account_code || "4040",
|
||||
})
|
||||
}
|
||||
@@ -641,14 +670,14 @@ class BrightpearlService extends BaseService {
|
||||
return client.orders
|
||||
.createCredit(order)
|
||||
.then(async (creditId) => {
|
||||
const paymentMethod = fromOrder.payment_method
|
||||
const paymentMethod = fromOrder.payments[0]
|
||||
const paymentType = "PAYMENT"
|
||||
const payment = {
|
||||
transactionRef: `${paymentMethod._id}.${paymentType}-${fromSwap._id}`,
|
||||
transactionCode: fromSwap._id,
|
||||
transactionRef: `${paymentMethod.id}.${paymentType}-${fromSwap.id}`,
|
||||
transactionCode: fromSwap.id,
|
||||
paymentMethodCode: this.options.payment_method_code || "1220",
|
||||
orderId: creditId,
|
||||
currencyIsoCode: fromSwap.currency_code,
|
||||
currencyIsoCode: fromOrder.currency_code.toUpperCase(),
|
||||
amountPaid: total,
|
||||
paymentDate: new Date(),
|
||||
paymentType,
|
||||
@@ -660,7 +689,12 @@ class BrightpearlService extends BaseService {
|
||||
}
|
||||
}
|
||||
|
||||
async createPayment(fromOrder) {
|
||||
async createPayment(fromOrderId) {
|
||||
const fromOrder = await this.orderService_.retrieve(fromOrderId, {
|
||||
select: ["total"],
|
||||
relations: ["payments"],
|
||||
})
|
||||
|
||||
const client = await this.getClient()
|
||||
const soId =
|
||||
fromOrder.metadata && fromOrder.metadata.brightpearl_sales_order_id
|
||||
@@ -669,38 +703,35 @@ class BrightpearlService extends BaseService {
|
||||
}
|
||||
|
||||
const paymentType = "RECEIPT"
|
||||
const paymentMethod = fromOrder.payment_method
|
||||
const payment = {
|
||||
transactionRef: `${paymentMethod._id}.${paymentType}`, // Brightpearl cannot accept an auth and capture with same ref
|
||||
transactionCode: fromOrder._id,
|
||||
transactionRef: `${fromOrder.id}.${paymentType}`, // Brightpearl cannot accept an auth and capture with same ref
|
||||
transactionCode: fromOrder.id,
|
||||
paymentMethodCode: this.options.payment_method_code || "1220",
|
||||
orderId: soId,
|
||||
paymentDate: new Date(),
|
||||
currencyIsoCode: fromOrder.currency_code,
|
||||
amountPaid: await this.totalsService_.getTotal(fromOrder),
|
||||
currencyIsoCode: fromOrder.currency_code.toUpperCase(),
|
||||
amountPaid: this.bpnum_(fromOrder.total),
|
||||
paymentType,
|
||||
}
|
||||
|
||||
await client.payments.create(payment)
|
||||
return client.payments.create(payment)
|
||||
}
|
||||
|
||||
async getBrightpearlRows(fromOrder) {
|
||||
const region = await this.regionService_.retrieve(fromOrder.region_id)
|
||||
const { region } = fromOrder
|
||||
const discount = fromOrder.discounts.find(
|
||||
({ discount_rule }) => discount_rule.type !== "free_shipping"
|
||||
({ rule }) => rule.type !== "free_shipping"
|
||||
)
|
||||
let lineDiscounts = []
|
||||
if (discount && !discount.is_giftcard) {
|
||||
if (discount) {
|
||||
lineDiscounts = this.totalsService_.getLineDiscounts(fromOrder, discount)
|
||||
}
|
||||
|
||||
const lines = await Promise.all(
|
||||
fromOrder.items.map(async (item) => {
|
||||
const bpProduct = await this.retrieveProductBySKU(
|
||||
item.content.variant.sku
|
||||
)
|
||||
const bpProduct = await this.retrieveProductBySKU(item.variant.sku)
|
||||
|
||||
const ld = lineDiscounts.find((l) => item._id === l.item._id) || {
|
||||
const ld = lineDiscounts.find((l) => item.id === l.item.id) || {
|
||||
amount: 0,
|
||||
}
|
||||
|
||||
@@ -710,13 +741,14 @@ class BrightpearlService extends BaseService {
|
||||
} else {
|
||||
row.name = item.title
|
||||
}
|
||||
row.net = this.totalsService_.rounded(
|
||||
item.content.unit_price * item.quantity - ld.amount
|
||||
row.net = this.bpnum_(item.unit_price * item.quantity - ld.amount)
|
||||
row.tax = this.bpnum_(
|
||||
item.unit_price * item.quantity - ld.amount,
|
||||
fromOrder.tax_rate
|
||||
)
|
||||
row.tax = this.totalsService_.rounded(row.net * fromOrder.tax_rate)
|
||||
row.quantity = item.quantity
|
||||
row.taxCode = region.tax_code
|
||||
row.externalRef = item._id
|
||||
row.externalRef = item.id
|
||||
row.nominalCode = this.options.sales_account_code || "4000"
|
||||
|
||||
if (item.is_giftcard) {
|
||||
@@ -731,30 +763,28 @@ class BrightpearlService extends BaseService {
|
||||
// correspondingly. This reduces the amount payable, while debiting the
|
||||
// gift card account that was previously credited, when the gift card was
|
||||
// purchased.
|
||||
if (discount && discount.is_giftcard) {
|
||||
const discountTotal = await this.totalsService_.getDiscountTotal(
|
||||
fromOrder
|
||||
)
|
||||
const gcTotal = fromOrder.gift_card_total
|
||||
if (gcTotal) {
|
||||
lines.push({
|
||||
name: `Gift Card: ${discount.code}`,
|
||||
net: -1 * discountTotal,
|
||||
tax: this.totalsService_.rounded(
|
||||
-1 * discountTotal * fromOrder.tax_rate
|
||||
),
|
||||
name: `Gift Card`,
|
||||
net: this.bpnum_(-1 * gcTotal),
|
||||
tax: this.bpnum_(-1 * gcTotal, fromOrder.tax_rate),
|
||||
quantity: 1,
|
||||
taxCode: region.tax_code,
|
||||
nominalCode: this.options.gift_card_account_code || "4000",
|
||||
})
|
||||
}
|
||||
|
||||
const shippingTotal = this.totalsService_.getShippingTotal(fromOrder)
|
||||
const shippingTotal =
|
||||
fromOrder.shipping_total ||
|
||||
this.totalsService_.getShippingTotal(fromOrder)
|
||||
const shippingMethods = fromOrder.shipping_methods
|
||||
if (shippingMethods.length > 0) {
|
||||
lines.push({
|
||||
name: `Shipping: ${shippingMethods.map((m) => m.name).join(" + ")}`,
|
||||
quantity: 1,
|
||||
net: this.totalsService_.rounded(shippingTotal),
|
||||
tax: this.totalsService_.rounded(shippingTotal * fromOrder.tax_rate),
|
||||
net: this.bpnum_(shippingTotal),
|
||||
tax: this.bpnum_(shippingTotal, fromOrder.tax_rate),
|
||||
taxCode: region.tax_code,
|
||||
nominalCode: this.options.shipping_account_code || "4040",
|
||||
})
|
||||
@@ -821,11 +851,10 @@ class BrightpearlService extends BaseService {
|
||||
.filter((i) => !!i)
|
||||
|
||||
// Orders with a concatenated externalReference are swap orders
|
||||
const [orderId, swapId] = order.externalRef.split(".")
|
||||
const [_, swapId] = order.externalRef.split(".")
|
||||
|
||||
if (swapId) {
|
||||
const order = await this.orderService_.retrieve(orderId)
|
||||
return this.swapService_.createFulfillment(order, swapId, {
|
||||
return this.swapService_.createFulfillment(swapId, {
|
||||
goods_out_note: id,
|
||||
})
|
||||
}
|
||||
@@ -866,6 +895,11 @@ class BrightpearlService extends BaseService {
|
||||
|
||||
return { contactId: customer }
|
||||
}
|
||||
|
||||
bpnum_(number, taxRate = 100) {
|
||||
const bpNumber = number / 100
|
||||
return this.totalsService_.rounded(bpNumber * (taxRate / 100))
|
||||
}
|
||||
}
|
||||
|
||||
export default BrightpearlService
|
||||
@@ -3,18 +3,24 @@ class OrderSubscriber {
|
||||
eventBusService,
|
||||
orderService,
|
||||
swapService,
|
||||
returnService,
|
||||
paymentProviderService,
|
||||
brightpearlService,
|
||||
fulfillmentService,
|
||||
}) {
|
||||
this.orderService_ = orderService
|
||||
this.brightpearlService_ = brightpearlService
|
||||
this.swapService_ = swapService
|
||||
this.returnService_ = returnService
|
||||
this.paymentProviderService_ = paymentProviderService
|
||||
this.fulfillmentService_ = fulfillmentService
|
||||
|
||||
eventBusService.subscribe("order.placed", this.sendToBrightpearl)
|
||||
|
||||
eventBusService.subscribe("order.refund_created", this.registerRefund)
|
||||
|
||||
eventBusService.subscribe("order.items_returned", this.registerReturn)
|
||||
|
||||
eventBusService.subscribe("order.placed", this.sendToBrightpearl)
|
||||
|
||||
eventBusService.subscribe(
|
||||
"order.payment_captured",
|
||||
this.registerCapturedPayment
|
||||
@@ -24,50 +30,65 @@ class OrderSubscriber {
|
||||
eventBusService.subscribe("swap.shipment_created", this.registerShipment)
|
||||
|
||||
// Before we initiate a swap we wait for the payment and the return
|
||||
eventBusService.subscribe("swap.payment_completed", this.registerSwap)
|
||||
eventBusService.subscribe(
|
||||
"swap.payment_completed",
|
||||
this.registerSwapPayment
|
||||
)
|
||||
eventBusService.subscribe("order.swap_received", this.registerSwap)
|
||||
}
|
||||
|
||||
sendToBrightpearl = (order) => {
|
||||
return this.brightpearlService_.createSalesOrder(order)
|
||||
sendToBrightpearl = (data) => {
|
||||
return this.brightpearlService_.createSalesOrder(data.id)
|
||||
}
|
||||
|
||||
registerCapturedPayment = (order) => {
|
||||
return this.brightpearlService_.createPayment(order)
|
||||
registerCapturedPayment = ({ id }) => {
|
||||
return this.brightpearlService_.createPayment(id)
|
||||
}
|
||||
|
||||
registerSwapPayment = async (data) => {
|
||||
return this.registerSwap({ id: data.id, swap_id: data.id })
|
||||
}
|
||||
|
||||
registerSwap = async (data) => {
|
||||
const { order, swap, swap_id } = data
|
||||
const { id, swap_id } = data
|
||||
|
||||
if (!order && !swap) {
|
||||
if (!id && !swap_id) {
|
||||
return
|
||||
}
|
||||
|
||||
let fromOrder = order
|
||||
if (!fromOrder) {
|
||||
fromOrder = await this.orderService_.retrieve(swap.order_id)
|
||||
}
|
||||
const fromSwap = await this.swapService_.retrieve(swap_id, {
|
||||
relations: [
|
||||
"order",
|
||||
"order.payments",
|
||||
"order.region",
|
||||
"order.swaps",
|
||||
"order.discounts",
|
||||
"return_order",
|
||||
"return_order.items",
|
||||
"return_order.shipping_method",
|
||||
"additional_items",
|
||||
"shipping_address",
|
||||
"shipping_methods",
|
||||
],
|
||||
})
|
||||
let fromOrder = fromSwap.order
|
||||
|
||||
let fromSwap
|
||||
if (swap) {
|
||||
fromSwap = await this.swapService_.retrieve(swap._id)
|
||||
} else {
|
||||
fromSwap = await this.swapService_.retrieve(swap_id)
|
||||
}
|
||||
|
||||
if (!(fromSwap.is_paid && fromSwap.return.status === "received")) {
|
||||
if (
|
||||
!(
|
||||
fromSwap.confirmed_at !== null &&
|
||||
fromSwap.return_order.status === "received"
|
||||
)
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
await this.brightpearlService_.createSwapCredit(fromOrder, fromSwap)
|
||||
await this.brightpearlService_.createSwapOrder(fromOrder, fromSwap)
|
||||
|
||||
const paySwap = await this.swapService_.retrieve(fromSwap._id)
|
||||
await this.brightpearlService_.createSwapPayment(paySwap)
|
||||
}
|
||||
|
||||
registerShipment = async (data) => {
|
||||
const { shipment } = data
|
||||
const { fulfillment_id } = data
|
||||
const shipment = await this.fulfillmentService_.retrieve(fulfillment_id)
|
||||
const noteId = shipment.metadata.goods_out_note
|
||||
if (noteId) {
|
||||
await this.brightpearlService_.registerGoodsOutTrackingNumber(
|
||||
@@ -78,15 +99,28 @@ class OrderSubscriber {
|
||||
}
|
||||
}
|
||||
|
||||
registerReturn = (data) => {
|
||||
const { order, return: fromReturn } = data
|
||||
registerReturn = async (data) => {
|
||||
const { id, return_id } = data
|
||||
|
||||
const order = await this.orderService_.retrieve(id, {
|
||||
relations: ["region", "payments"],
|
||||
})
|
||||
|
||||
const fromReturn = await this.returnService_.retrieve(return_id, {
|
||||
relations: ["items"],
|
||||
})
|
||||
|
||||
return this.brightpearlService_
|
||||
.createSalesCredit(order, fromReturn)
|
||||
.catch((err) => console.log(err))
|
||||
}
|
||||
|
||||
registerRefund = (data) => {
|
||||
const { order, refund } = data
|
||||
registerRefund = async (data) => {
|
||||
const { id, refund_id } = data
|
||||
const order = await this.orderService_.retrieve(id, {
|
||||
relations: ["region", "payments"],
|
||||
})
|
||||
const refund = await this.paymentProviderService_.retrieveRefund(refund_id)
|
||||
return this.brightpearlService_
|
||||
.createRefundCredit(order, refund)
|
||||
.catch((err) => console.log(err))
|
||||
|
||||
@@ -5,5 +5,9 @@ node_modules
|
||||
/*.js
|
||||
!index.js
|
||||
yarn.lock
|
||||
|
||||
src
|
||||
.gitignore
|
||||
.eslintrc
|
||||
.babelrc
|
||||
.prettierrc
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "medusa-plugin-contentful",
|
||||
"version": "1.0.15",
|
||||
"version": "1.0.12-alpha.176+0646bd3",
|
||||
"description": "Contentful plugin for Medusa Commerce",
|
||||
"main": "index.js",
|
||||
"repository": {
|
||||
@@ -39,9 +39,9 @@
|
||||
"body-parser": "^1.19.0",
|
||||
"contentful-management": "^5.27.1",
|
||||
"express": "^4.17.1",
|
||||
"medusa-core-utils": "^1.0.11",
|
||||
"medusa-test-utils": "^1.0.13",
|
||||
"medusa-core-utils": "^1.0.12-alpha.787+0646bd3",
|
||||
"medusa-test-utils": "^1.0.12-alpha.176+0646bd3",
|
||||
"redis": "^3.0.2"
|
||||
},
|
||||
"gitHead": "3cc7cbe5124cbcbb75f6e1435db4dcfaa2a60408"
|
||||
"gitHead": "0646bd395a6056657cb0aa93c13699c4a9dbbcdd"
|
||||
}
|
||||
@@ -46,21 +46,16 @@ class ContentfulService extends BaseService {
|
||||
}
|
||||
}
|
||||
|
||||
async getVariantEntries_(productId) {
|
||||
async getVariantEntries_(variants) {
|
||||
try {
|
||||
const productVariants = await this.productService_.retrieveVariants(
|
||||
productId
|
||||
)
|
||||
|
||||
const contentfulVariants = await Promise.all(
|
||||
productVariants.map((variant) =>
|
||||
variants.map((variant) =>
|
||||
this.updateProductVariantInContentful(variant)
|
||||
)
|
||||
)
|
||||
|
||||
return contentfulVariants
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
@@ -118,79 +113,33 @@ class ContentfulService extends BaseService {
|
||||
|
||||
async createProductInContentful(product) {
|
||||
try {
|
||||
const p = await this.productService_.retrieve(product.id, {
|
||||
relations: ["variants", "options"],
|
||||
})
|
||||
|
||||
const environment = await this.getContentfulEnvironment_()
|
||||
const variantEntries = await this.getVariantEntries_(product._id)
|
||||
const variantEntries = await this.getVariantEntries_(p.variants)
|
||||
const variantLinks = this.getVariantLinks_(variantEntries)
|
||||
|
||||
const fieldsObject = {
|
||||
title: {
|
||||
"en-US": product.title,
|
||||
},
|
||||
variants: {
|
||||
"en-US": variantLinks,
|
||||
},
|
||||
options: {
|
||||
"en-US": product.options,
|
||||
},
|
||||
objectId: {
|
||||
"en-US": product._id,
|
||||
},
|
||||
}
|
||||
|
||||
if (product.images.length > 0) {
|
||||
const imageLinks = await this.createImageAssets(product)
|
||||
|
||||
const thumbnailAsset = await environment.createAsset({
|
||||
fields: {
|
||||
title: {
|
||||
"en-US": `${product.title}`,
|
||||
},
|
||||
description: {
|
||||
"en-US": "",
|
||||
},
|
||||
file: {
|
||||
"en-US": {
|
||||
contentType: "image/xyz",
|
||||
fileName: product.thumbnail,
|
||||
upload: product.thumbnail,
|
||||
},
|
||||
},
|
||||
const result = await environment.createEntryWithId("product", p.id, {
|
||||
fields: {
|
||||
title: {
|
||||
"en-US": p.title,
|
||||
},
|
||||
})
|
||||
|
||||
await thumbnailAsset.processForAllLocales()
|
||||
|
||||
const thumbnailLink = {
|
||||
sys: {
|
||||
type: "Link",
|
||||
linkType: "Asset",
|
||||
id: thumbnailAsset.sys.id,
|
||||
variants: {
|
||||
"en-US": variantLinks,
|
||||
},
|
||||
}
|
||||
|
||||
fieldsObject.thumbnail = {
|
||||
"en-US": thumbnailLink,
|
||||
}
|
||||
|
||||
if (imageLinks) {
|
||||
fieldsObject.images = {
|
||||
"en-US": imageLinks,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const result = await environment.createEntryWithId(
|
||||
"product",
|
||||
product._id,
|
||||
{
|
||||
fields: {
|
||||
...fieldsObject,
|
||||
options: {
|
||||
"en-US": p.options,
|
||||
},
|
||||
}
|
||||
)
|
||||
objectId: {
|
||||
"en-US": p.id,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
const ignoreIds = (await this.getIgnoreIds_("product")) || []
|
||||
ignoreIds.push(product._id)
|
||||
ignoreIds.push(product.id)
|
||||
this.redis_.set("product_ignore_ids", JSON.stringify(ignoreIds))
|
||||
return result
|
||||
} catch (error) {
|
||||
@@ -200,33 +149,37 @@ class ContentfulService extends BaseService {
|
||||
|
||||
async createProductVariantInContentful(variant) {
|
||||
try {
|
||||
const v = await this.productVariantService_.retrieve(variant.id, {
|
||||
relations: ["prices", "options"],
|
||||
})
|
||||
|
||||
const environment = await this.getContentfulEnvironment_()
|
||||
const result = await environment.createEntryWithId(
|
||||
"productVariant",
|
||||
variant._id,
|
||||
v.id,
|
||||
{
|
||||
fields: {
|
||||
title: {
|
||||
"en-US": variant.title,
|
||||
"en-US": v.title,
|
||||
},
|
||||
sku: {
|
||||
"en-US": variant.sku,
|
||||
"en-US": v.sku,
|
||||
},
|
||||
prices: {
|
||||
"en-US": variant.prices,
|
||||
"en-US": v.prices,
|
||||
},
|
||||
options: {
|
||||
"en-US": variant.options,
|
||||
"en-US": v.options,
|
||||
},
|
||||
objectId: {
|
||||
"en-US": variant._id,
|
||||
"en-US": v.id,
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
const ignoreIds = (await this.getIgnoreIds_("product_variant")) || []
|
||||
ignoreIds.push(variant._id)
|
||||
ignoreIds.push(v.id)
|
||||
this.redis_.set("product_variant_ignore_ids", JSON.stringify(ignoreIds))
|
||||
return result
|
||||
} catch (error) {
|
||||
@@ -238,12 +191,12 @@ class ContentfulService extends BaseService {
|
||||
try {
|
||||
const ignoreIds = (await this.getIgnoreIds_("product")) || []
|
||||
|
||||
if (ignoreIds.includes(product._id)) {
|
||||
const newIgnoreIds = ignoreIds.filter((id) => id !== product._id)
|
||||
if (ignoreIds.includes(product.id)) {
|
||||
const newIgnoreIds = ignoreIds.filter((id) => id !== product.id)
|
||||
this.redis_.set("product_ignore_ids", JSON.stringify(newIgnoreIds))
|
||||
return
|
||||
} else {
|
||||
ignoreIds.push(product._id)
|
||||
ignoreIds.push(product.id)
|
||||
this.redis_.set("product_ignore_ids", JSON.stringify(ignoreIds))
|
||||
}
|
||||
|
||||
@@ -251,27 +204,31 @@ class ContentfulService extends BaseService {
|
||||
// check if product exists
|
||||
let productEntry = undefined
|
||||
try {
|
||||
productEntry = await environment.getEntry(product._id)
|
||||
productEntry = await environment.getEntry(product.id)
|
||||
} catch (error) {
|
||||
return this.createProductInContentful(product)
|
||||
}
|
||||
|
||||
const variantEntries = await this.getVariantEntries_(product._id)
|
||||
const p = await this.productService_.retrieve(product.id, {
|
||||
relations: ["options", "variants"],
|
||||
})
|
||||
|
||||
const variantEntries = await this.getVariantEntries_(p.variants)
|
||||
const variantLinks = this.getVariantLinks_(variantEntries)
|
||||
|
||||
const productEntryFields = {
|
||||
...productEntry.fields,
|
||||
title: {
|
||||
"en-US": product.title,
|
||||
"en-US": p.title,
|
||||
},
|
||||
options: {
|
||||
"en-US": product.options,
|
||||
"en-US": p.options,
|
||||
},
|
||||
variants: {
|
||||
"en-US": variantLinks,
|
||||
},
|
||||
objectId: {
|
||||
"en-US": product._id,
|
||||
"en-US": p.id,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -290,15 +247,15 @@ class ContentfulService extends BaseService {
|
||||
try {
|
||||
const ignoreIds = (await this.getIgnoreIds_("product_variant")) || []
|
||||
|
||||
if (ignoreIds.includes(variant._id)) {
|
||||
const newIgnoreIds = ignoreIds.filter((id) => id !== variant._id)
|
||||
if (ignoreIds.includes(variant.id)) {
|
||||
const newIgnoreIds = ignoreIds.filter((id) => id !== variant.id)
|
||||
this.redis_.set(
|
||||
"product_variant_ignore_ids",
|
||||
JSON.stringify(newIgnoreIds)
|
||||
)
|
||||
return
|
||||
} else {
|
||||
ignoreIds.push(variant._id)
|
||||
ignoreIds.push(variant.id)
|
||||
this.redis_.set("product_variant_ignore_ids", JSON.stringify(ignoreIds))
|
||||
}
|
||||
|
||||
@@ -307,32 +264,31 @@ class ContentfulService extends BaseService {
|
||||
let variantEntry = undefined
|
||||
// if not, we create a new one
|
||||
try {
|
||||
variantEntry = await environment.getEntry(variant._id)
|
||||
variantEntry = await environment.getEntry(variant.id)
|
||||
} catch (error) {
|
||||
return this.createProductVariantInContentful(variant)
|
||||
}
|
||||
|
||||
const cleanPrices = variant.prices.map((price) => ({
|
||||
...price,
|
||||
sale_amount: price.sale_amount || undefined,
|
||||
}))
|
||||
const v = await this.productVariantService_.retrieve(variant.id, {
|
||||
relations: ["prices", "options"],
|
||||
})
|
||||
|
||||
const variantEntryFields = {
|
||||
...variantEntry.fields,
|
||||
title: {
|
||||
"en-US": variant.title,
|
||||
"en-US": v.title,
|
||||
},
|
||||
sku: {
|
||||
"en-US": variant.sku,
|
||||
"en-US": v.sku,
|
||||
},
|
||||
options: {
|
||||
"en-US": variant.options,
|
||||
"en-US": v.options,
|
||||
},
|
||||
prices: {
|
||||
"en-US": cleanPrices,
|
||||
"en-US": v.prices,
|
||||
},
|
||||
objectId: {
|
||||
"en-US": variant._id,
|
||||
"en-US": v.id,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
class ContentfulSubscriber {
|
||||
constructor({ contentfulService, eventBusService }) {
|
||||
constructor({
|
||||
contentfulService,
|
||||
productVariantService,
|
||||
productService,
|
||||
eventBusService,
|
||||
}) {
|
||||
this.productVariantService_ = productVariantService
|
||||
this.productService_ = productService
|
||||
this.contentfulService_ = contentfulService
|
||||
this.eventBus_ = eventBusService
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "medusa-plugin-discount-generator",
|
||||
"version": "1.0.1",
|
||||
"version": "1.0.2-alpha.787+0646bd3",
|
||||
"main": "index.js",
|
||||
"license": "MIT",
|
||||
"author": "Sebastian Rindom",
|
||||
@@ -40,5 +40,5 @@
|
||||
"medusa-core-utils": "^0.1.27",
|
||||
"randomatic": "^3.1.1"
|
||||
},
|
||||
"gitHead": "3cc7cbe5124cbcbb75f6e1435db4dcfaa2a60408"
|
||||
"gitHead": "0646bd395a6056657cb0aa93c13699c4a9dbbcdd"
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "medusa-plugin-economic",
|
||||
"version": "1.0.13",
|
||||
"version": "1.0.12-alpha.176+0646bd3",
|
||||
"description": "E-conomic financial reporting",
|
||||
"main": "index.js",
|
||||
"repository": {
|
||||
@@ -39,9 +39,9 @@
|
||||
"axios": "^0.19.2",
|
||||
"body-parser": "^1.19.0",
|
||||
"express": "^4.17.1",
|
||||
"medusa-core-utils": "^1.0.11",
|
||||
"medusa-test-utils": "^1.0.13",
|
||||
"medusa-core-utils": "^1.0.12-alpha.787+0646bd3",
|
||||
"medusa-test-utils": "^1.0.12-alpha.176+0646bd3",
|
||||
"moment": "^2.27.0"
|
||||
},
|
||||
"gitHead": "3cc7cbe5124cbcbb75f6e1435db4dcfaa2a60408"
|
||||
"gitHead": "0646bd395a6056657cb0aa93c13699c4a9dbbcdd"
|
||||
}
|
||||
@@ -71,7 +71,7 @@ class EconomicService extends BaseService {
|
||||
let order_lines = []
|
||||
// Find the discount, that is not free shipping
|
||||
const discount = order.discounts.find(
|
||||
({ discount_rule }) => discount_rule.type !== "free_shipping"
|
||||
({ rule }) => rule.type !== "free_shipping"
|
||||
)
|
||||
// If the discount has an item specific allocation method,
|
||||
// we need to fetch the discount for each item
|
||||
|
||||
@@ -5,5 +5,9 @@ node_modules
|
||||
/*.js
|
||||
!index.js
|
||||
yarn.lock
|
||||
|
||||
src
|
||||
.gitignore
|
||||
.eslintrc
|
||||
.babelrc
|
||||
.prettierrc
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "medusa-plugin-ip-lookup",
|
||||
"version": "1.0.11",
|
||||
"version": "1.0.12-alpha.787+0646bd3",
|
||||
"description": "IP lookup middleware for Medusa core",
|
||||
"main": "dist/index.js",
|
||||
"repository": {
|
||||
@@ -35,5 +35,5 @@
|
||||
"axios": "^0.20.0",
|
||||
"mongoose": "^5.8.0"
|
||||
},
|
||||
"gitHead": "3cc7cbe5124cbcbb75f6e1435db4dcfaa2a60408"
|
||||
"gitHead": "0646bd395a6056657cb0aa93c13699c4a9dbbcdd"
|
||||
}
|
||||
@@ -7,7 +7,8 @@ export default {
|
||||
}
|
||||
|
||||
const ipLookupService = req.scope.resolve("ipLookupService")
|
||||
const regionService = req.scope.resolve("regionService")
|
||||
const manager = req.scope.resolve("manager")
|
||||
const countryRepository = req.scope.resolve("countryRepository")
|
||||
|
||||
const ip = req.headers["x-forwarded-for"] || req.connection.remoteAddress
|
||||
|
||||
@@ -18,15 +19,15 @@ export default {
|
||||
return
|
||||
}
|
||||
|
||||
// Find region using the country code from ip lookup
|
||||
const regions = await regionService.list({
|
||||
countries: data.country_code,
|
||||
const countryRepo = manager.getCustomRepository(countryRepository)
|
||||
const country = await countryRepo.findOne({
|
||||
where: { iso_2: data.country_code.toLowerCase() },
|
||||
})
|
||||
|
||||
// If this region exists, add it to the body of the cart creation request
|
||||
if (regions[0]) {
|
||||
req.body.region_id = regions[0]._id.toString()
|
||||
req.body.country_code = data.country_code
|
||||
// If country exists, add it to the body of the cart creation request
|
||||
if (country) {
|
||||
req.body.region_id = country.region_id
|
||||
req.body.country_code = country.iso_2
|
||||
}
|
||||
|
||||
next()
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "medusa-plugin-mailchimp",
|
||||
"version": "1.0.15",
|
||||
"version": "1.0.14-alpha.176+0646bd3",
|
||||
"description": "Mailchimp newsletter subscriptions",
|
||||
"main": "index.js",
|
||||
"repository": {
|
||||
@@ -40,7 +40,8 @@
|
||||
"cors": "^2.8.5",
|
||||
"express": "^4.17.1",
|
||||
"mailchimp-api-v3": "^1.14.0",
|
||||
"medusa-core-utils": "^1.0.11",
|
||||
"medusa-test-utils": "^1.0.13"
|
||||
}
|
||||
"medusa-core-utils": "^1.0.12-alpha.787+0646bd3",
|
||||
"medusa-test-utils": "^1.0.12-alpha.176+0646bd3"
|
||||
},
|
||||
"gitHead": "0646bd395a6056657cb0aa93c13699c4a9dbbcdd"
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "medusa-plugin-permissions",
|
||||
"version": "1.0.13",
|
||||
"version": "1.0.12-alpha.176+0646bd3",
|
||||
"description": "Role permission for Medusa core",
|
||||
"main": "dist/index.js",
|
||||
"repository": {
|
||||
@@ -32,9 +32,9 @@
|
||||
"medusa-interfaces": "1.x"
|
||||
},
|
||||
"dependencies": {
|
||||
"medusa-core-utils": "^1.0.11",
|
||||
"medusa-test-utils": "^1.0.13",
|
||||
"medusa-core-utils": "^1.0.12-alpha.787+0646bd3",
|
||||
"medusa-test-utils": "^1.0.12-alpha.176+0646bd3",
|
||||
"mongoose": "^5.8.0"
|
||||
},
|
||||
"gitHead": "3cc7cbe5124cbcbb75f6e1435db4dcfaa2a60408"
|
||||
"gitHead": "0646bd395a6056657cb0aa93c13699c4a9dbbcdd"
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "medusa-plugin-segment",
|
||||
"version": "1.0.16",
|
||||
"version": "1.0.14-alpha.176+0646bd3",
|
||||
"description": "Segment Analytics",
|
||||
"main": "index.js",
|
||||
"repository": {
|
||||
@@ -39,8 +39,8 @@
|
||||
"axios": "^0.19.2",
|
||||
"body-parser": "^1.19.0",
|
||||
"express": "^4.17.1",
|
||||
"medusa-core-utils": "^1.0.11",
|
||||
"medusa-test-utils": "^1.0.13"
|
||||
"medusa-core-utils": "^1.0.12-alpha.787+0646bd3",
|
||||
"medusa-test-utils": "^1.0.12-alpha.176+0646bd3"
|
||||
},
|
||||
"gitHead": "3cc7cbe5124cbcbb75f6e1435db4dcfaa2a60408"
|
||||
"gitHead": "0646bd395a6056657cb0aa93c13699c4a9dbbcdd"
|
||||
}
|
||||
@@ -13,8 +13,8 @@ class SegmentService extends BaseService {
|
||||
constructor({ totalsService }, options) {
|
||||
super()
|
||||
|
||||
this.options_ = options
|
||||
this.totalsService_ = totalsService
|
||||
this.options_ = options
|
||||
|
||||
this.analytics_ = new Analytics(options.write_key)
|
||||
}
|
||||
@@ -30,14 +30,17 @@ class SegmentService extends BaseService {
|
||||
return this.analytics_.track(data)
|
||||
}
|
||||
|
||||
async getReportingValue(fromCurrency, value) {
|
||||
async getReportingValue(rawCurrency, value) {
|
||||
const fromCurrency = rawCurrency.toUpperCase()
|
||||
const date = "latest"
|
||||
const toCurrency =
|
||||
(this.options_.reporting_currency &&
|
||||
this.options_.reporting_currency.toUpperCase()) ||
|
||||
"EUR"
|
||||
|
||||
if (fromCurrency === toCurrency) return value.toFixed(2)
|
||||
if (fromCurrency === toCurrency) {
|
||||
return this.totalsService_.rounded(value)
|
||||
}
|
||||
|
||||
const exchangeRate = await axios
|
||||
.get(
|
||||
@@ -47,28 +50,28 @@ class SegmentService extends BaseService {
|
||||
return data.rates[fromCurrency]
|
||||
})
|
||||
|
||||
return (value / exchangeRate).toFixed(2)
|
||||
return this.totalsService_.rounded(value / exchangeRate)
|
||||
}
|
||||
|
||||
async buildOrder(order) {
|
||||
const subtotal = await this.totalsService_.getSubtotal(order)
|
||||
const total = await this.totalsService_.getTotal(order)
|
||||
const tax = await this.totalsService_.getTaxTotal(order)
|
||||
const discount = await this.totalsService_.getDiscountTotal(order)
|
||||
const shipping = await this.totalsService_.getShippingTotal(order)
|
||||
const subtotal = order.subtotal / 100
|
||||
const total = order.total / 100
|
||||
const tax = order.tax_total / 100
|
||||
const discount = order.discount_total / 100
|
||||
const shipping = order.shipping_total / 100
|
||||
const revenue = total - tax
|
||||
|
||||
let coupon
|
||||
if (order.discounts && order.discounts.length) {
|
||||
coupon = order.discounts[0].code
|
||||
coupon = order.discounts[0] && order.discounts[0].code
|
||||
}
|
||||
|
||||
const orderData = {
|
||||
checkout_id: order.cart_id,
|
||||
order_id: order._id,
|
||||
order_id: order.id,
|
||||
email: order.email,
|
||||
region_id: order.region_id,
|
||||
payment_provider: order.payment_method.provider_id,
|
||||
payment_provider: order.payments.map((p) => p.provider_id).join(","),
|
||||
shipping_methods: order.shipping_methods,
|
||||
shipping_country: order.shipping_address.country_code,
|
||||
shipping_city: order.shipping_address.city,
|
||||
@@ -102,8 +105,8 @@ class SegmentService extends BaseService {
|
||||
order.items.map(async (item) => {
|
||||
let name = item.title
|
||||
|
||||
const unit_price = item.content.unit_price
|
||||
const line_total = unit_price * item.content.quantity * item.quantity
|
||||
const unit_price = item.unit_price
|
||||
const line_total = (unit_price * item.quantity) / 100
|
||||
const revenue = await this.getReportingValue(
|
||||
order.currency_code,
|
||||
line_total
|
||||
@@ -114,11 +117,11 @@ class SegmentService extends BaseService {
|
||||
|
||||
return {
|
||||
name,
|
||||
variant: item.content.variant.sku,
|
||||
price: unit_price,
|
||||
variant,
|
||||
price: unit_price / 100,
|
||||
reporting_revenue: revenue,
|
||||
product_id: `${item.content.product._id}`,
|
||||
sku: skuParts.join("-"),
|
||||
product_id: item.variant.product_id,
|
||||
sku: item.variant.sku,
|
||||
quantity: item.quantity,
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1,13 +1,55 @@
|
||||
class OrderSubscriber {
|
||||
constructor({ segmentService, eventBusService }) {
|
||||
constructor({
|
||||
segmentService,
|
||||
eventBusService,
|
||||
orderService,
|
||||
returnService,
|
||||
}) {
|
||||
this.orderService_ = orderService
|
||||
|
||||
this.returnService_ = returnService
|
||||
|
||||
eventBusService.subscribe(
|
||||
"order.items_returned",
|
||||
async ({ order, return: ret }) => {
|
||||
async ({ id, return_id }) => {
|
||||
const order = await this.orderService_.retrieve(id, {
|
||||
select: [
|
||||
"shipping_total",
|
||||
"discount_total",
|
||||
"tax_total",
|
||||
"refunded_total",
|
||||
"gift_card_total",
|
||||
"subtotal",
|
||||
"total",
|
||||
],
|
||||
relations: [
|
||||
"customer",
|
||||
"billing_address",
|
||||
"shipping_address",
|
||||
"discounts",
|
||||
"shipping_methods",
|
||||
"payments",
|
||||
"fulfillments",
|
||||
"returns",
|
||||
"gift_cards",
|
||||
"gift_card_transactions",
|
||||
"swaps",
|
||||
"swaps.return_order",
|
||||
"swaps.payment",
|
||||
"swaps.shipping_methods",
|
||||
"swaps.shipping_address",
|
||||
"swaps.additional_items",
|
||||
"swaps.fulfillments",
|
||||
],
|
||||
})
|
||||
|
||||
const ret = await this.returnService_.retrieve(return_id)
|
||||
|
||||
const shipping = []
|
||||
if (ret.shipping_method && ret.shipping_method.price) {
|
||||
shipping.push({
|
||||
...ret.shipping_method,
|
||||
price: -1 * ret.shipping_method.price,
|
||||
price: -1 * (ret.shipping_method.price / 100),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -15,7 +57,7 @@ class OrderSubscriber {
|
||||
...order,
|
||||
shipping_methods: shipping,
|
||||
items: ret.items.map((i) =>
|
||||
order.items.find((l) => l._id === i.item_id)
|
||||
order.items.find((l) => l.id === i.item_id)
|
||||
),
|
||||
}
|
||||
|
||||
@@ -31,7 +73,19 @@ class OrderSubscriber {
|
||||
}
|
||||
)
|
||||
|
||||
eventBusService.subscribe("order.canceled", async (order) => {
|
||||
eventBusService.subscribe("order.canceled", async ({ id }) => {
|
||||
const order = await this.orderService_.retrieve(id, {
|
||||
select: [
|
||||
"shipping_total",
|
||||
"discount_total",
|
||||
"tax_total",
|
||||
"refunded_total",
|
||||
"gift_card_total",
|
||||
"subtotal",
|
||||
"total",
|
||||
],
|
||||
})
|
||||
|
||||
const date = new Date()
|
||||
const orderData = await segmentService.buildOrder(order)
|
||||
const orderEvent = {
|
||||
@@ -44,7 +98,38 @@ class OrderSubscriber {
|
||||
segmentService.track(orderEvent)
|
||||
})
|
||||
|
||||
eventBusService.subscribe("order.placed", async (order) => {
|
||||
eventBusService.subscribe("order.placed", async ({ id }) => {
|
||||
const order = await this.orderService_.retrieve(id, {
|
||||
select: [
|
||||
"shipping_total",
|
||||
"discount_total",
|
||||
"tax_total",
|
||||
"refunded_total",
|
||||
"gift_card_total",
|
||||
"subtotal",
|
||||
"total",
|
||||
],
|
||||
relations: [
|
||||
"customer",
|
||||
"billing_address",
|
||||
"shipping_address",
|
||||
"discounts",
|
||||
"shipping_methods",
|
||||
"payments",
|
||||
"fulfillments",
|
||||
"returns",
|
||||
"gift_cards",
|
||||
"gift_card_transactions",
|
||||
"swaps",
|
||||
"swaps.return_order",
|
||||
"swaps.payment",
|
||||
"swaps.shipping_methods",
|
||||
"swaps.shipping_address",
|
||||
"swaps.additional_items",
|
||||
"swaps.fulfillments",
|
||||
],
|
||||
})
|
||||
|
||||
const date = new Date(parseInt(order.created))
|
||||
const orderData = await segmentService.buildOrder(order)
|
||||
const orderEvent = {
|
||||
|
||||
@@ -5,5 +5,9 @@ node_modules
|
||||
/*.js
|
||||
!index.js
|
||||
yarn.lock
|
||||
|
||||
src
|
||||
.gitignore
|
||||
.eslintrc
|
||||
.babelrc
|
||||
.prettierrc
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "medusa-plugin-sendgrid",
|
||||
"version": "1.0.14",
|
||||
"version": "1.0.13-alpha.176+0646bd3",
|
||||
"description": "SendGrid transactional emails",
|
||||
"main": "index.js",
|
||||
"repository": {
|
||||
@@ -39,8 +39,8 @@
|
||||
"@sendgrid/mail": "^7.1.1",
|
||||
"body-parser": "^1.19.0",
|
||||
"express": "^4.17.1",
|
||||
"medusa-core-utils": "^1.0.11",
|
||||
"medusa-test-utils": "^1.0.13"
|
||||
"medusa-core-utils": "^1.0.12-alpha.787+0646bd3",
|
||||
"medusa-test-utils": "^1.0.12-alpha.176+0646bd3"
|
||||
},
|
||||
"gitHead": "3cc7cbe5124cbcbb75f6e1435db4dcfaa2a60408"
|
||||
"gitHead": "0646bd395a6056657cb0aa93c13699c4a9dbbcdd"
|
||||
}
|
||||
@@ -37,8 +37,7 @@ class SendGridService extends BaseService {
|
||||
templateId = this.options_.gift_card_created_template
|
||||
data = {
|
||||
...data,
|
||||
display_value:
|
||||
data.giftcard.discount_rule.value * (1 + data.tax_rate),
|
||||
display_value: data.giftcard.rule.value * (1 + data.tax_rate),
|
||||
}
|
||||
break
|
||||
case "order.placed":
|
||||
|
||||
@@ -4,16 +4,52 @@ class OrderSubscriber {
|
||||
orderService,
|
||||
sendgridService,
|
||||
eventBusService,
|
||||
fulfillmentService,
|
||||
}) {
|
||||
this.orderService_ = orderService
|
||||
this.totalsService_ = totalsService
|
||||
this.sendgridService_ = sendgridService
|
||||
this.eventBus_ = eventBusService
|
||||
this.fulfillmentService_ = fulfillmentService
|
||||
|
||||
this.eventBus_.subscribe(
|
||||
"order.shipment_created",
|
||||
async ({ order_id, shipment }) => {
|
||||
const order = await this.orderService_.retrieve(order_id)
|
||||
async ({ id, fulfillment_id }) => {
|
||||
const order = await this.orderService_.retrieve(id, {
|
||||
select: [
|
||||
"shipping_total",
|
||||
"discount_total",
|
||||
"tax_total",
|
||||
"refunded_total",
|
||||
"gift_card_total",
|
||||
"subtotal",
|
||||
"total",
|
||||
"refundable_amount",
|
||||
],
|
||||
relations: [
|
||||
"customer",
|
||||
"billing_address",
|
||||
"shipping_address",
|
||||
"discounts",
|
||||
"shipping_methods",
|
||||
"shipping_methods.shipping_option",
|
||||
"payments",
|
||||
"fulfillments",
|
||||
"returns",
|
||||
"gift_cards",
|
||||
"gift_card_transactions",
|
||||
"swaps",
|
||||
"swaps.return_order",
|
||||
"swaps.payment",
|
||||
"swaps.shipping_methods",
|
||||
"swaps.shipping_address",
|
||||
"swaps.additional_items",
|
||||
"swaps.fulfillments",
|
||||
],
|
||||
})
|
||||
|
||||
const shipment = await this.fulfillmentService_.retrieve(fulfillment_id)
|
||||
|
||||
const data = {
|
||||
...order,
|
||||
tracking_number: shipment.tracking_numbers.join(", "),
|
||||
@@ -33,50 +69,108 @@ class OrderSubscriber {
|
||||
)
|
||||
})
|
||||
|
||||
this.eventBus_.subscribe("order.placed", async (order) => {
|
||||
const subtotal = await this.totalsService_.getSubtotal(order)
|
||||
const tax_total = await this.totalsService_.getTaxTotal(order)
|
||||
const discount_total = await this.totalsService_.getDiscountTotal(order)
|
||||
const shipping_total = await this.totalsService_.getShippingTotal(order)
|
||||
const total = await this.totalsService_.getTotal(order)
|
||||
this.eventBus_.subscribe("order.placed", async (orderObj) => {
|
||||
try {
|
||||
const order = await this.orderService_.retrieve(orderObj.id, {
|
||||
select: [
|
||||
"shipping_total",
|
||||
"discount_total",
|
||||
"tax_total",
|
||||
"refunded_total",
|
||||
"gift_card_total",
|
||||
"subtotal",
|
||||
"total",
|
||||
],
|
||||
relations: [
|
||||
"customer",
|
||||
"billing_address",
|
||||
"shipping_address",
|
||||
"discounts",
|
||||
"shipping_methods",
|
||||
"shipping_methods.shipping_option",
|
||||
"payments",
|
||||
"fulfillments",
|
||||
"returns",
|
||||
"gift_cards",
|
||||
"gift_card_transactions",
|
||||
"swaps",
|
||||
"swaps.return_order",
|
||||
"swaps.payment",
|
||||
"swaps.shipping_methods",
|
||||
"swaps.shipping_address",
|
||||
"swaps.additional_items",
|
||||
"swaps.fulfillments",
|
||||
],
|
||||
})
|
||||
|
||||
const date = new Date(parseInt(order.created))
|
||||
const data = {
|
||||
...order,
|
||||
date: date.toDateString(),
|
||||
items: order.items.map((i) => {
|
||||
const {
|
||||
subtotal,
|
||||
tax_total,
|
||||
discount_total,
|
||||
shipping_total,
|
||||
total,
|
||||
} = order
|
||||
|
||||
const taxRate = order.tax_rate / 100
|
||||
const currencyCode = order.currency_code.toUpperCase()
|
||||
|
||||
const items = order.items.map((i) => {
|
||||
return {
|
||||
...i,
|
||||
price: `${(i.content.unit_price * (1 + order.tax_rate)).toFixed(
|
||||
price: `${((i.unit_price / 100) * (1 + taxRate)).toFixed(
|
||||
2
|
||||
)} ${order.currency_code}`,
|
||||
)} ${currencyCode}`,
|
||||
}
|
||||
}),
|
||||
discounts: order.discounts.map((discount) => {
|
||||
return {
|
||||
is_giftcard: discount.is_giftcard,
|
||||
code: discount.code,
|
||||
descriptor: `${discount.discount_rule.value}${
|
||||
discount.discount_rule.type === "percentage"
|
||||
? "%"
|
||||
: ` ${order.currency_code}`
|
||||
}`,
|
||||
}
|
||||
}),
|
||||
subtotal: `${(subtotal * (1 + order.tax_rate)).toFixed(2)} ${
|
||||
order.currency_code
|
||||
}`,
|
||||
tax_total: `${tax_total.toFixed(2)} ${order.currency_code}`,
|
||||
discount_total: `${(discount_total * (1 + order.tax_rate)).toFixed(
|
||||
2
|
||||
)} ${order.currency_code}`,
|
||||
shipping_total: `${(shipping_total * (1 + order.tax_rate)).toFixed(
|
||||
2
|
||||
)} ${order.currency_code}`,
|
||||
total: `${total.toFixed(2)} ${order.currency_code}`,
|
||||
}
|
||||
})
|
||||
|
||||
await this.sendgridService_.transactionalEmail("order.placed", data)
|
||||
let discounts = []
|
||||
if (order.discounts) {
|
||||
discounts = order.discounts.map((discount) => {
|
||||
return {
|
||||
is_giftcard: false,
|
||||
code: discount.code,
|
||||
descriptor: `${discount.rule.value}${
|
||||
discount.rule.type === "percentage" ? "%" : ` ${currencyCode}`
|
||||
}`,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
let giftCards = []
|
||||
if (order.gift_cards) {
|
||||
giftCards = order.gift_cards.map((gc) => {
|
||||
return {
|
||||
is_giftcard: true,
|
||||
code: gc.code,
|
||||
descriptor: `${gc.value} ${currencyCode}`,
|
||||
}
|
||||
})
|
||||
|
||||
discounts.concat(giftCards)
|
||||
}
|
||||
|
||||
const data = {
|
||||
...order,
|
||||
date: order.created_at.toDateString(),
|
||||
items,
|
||||
discounts,
|
||||
subtotal: `${((subtotal / 100) * (1 + taxRate)).toFixed(
|
||||
2
|
||||
)} ${currencyCode}`,
|
||||
tax_total: `${(tax_total / 100).toFixed(2)} ${currencyCode}`,
|
||||
discount_total: `${((discount_total / 100) * (1 + taxRate)).toFixed(
|
||||
2
|
||||
)} ${currencyCode}`,
|
||||
shipping_total: `${((shipping_total / 100) * (1 + taxRate)).toFixed(
|
||||
2
|
||||
)} ${currencyCode}`,
|
||||
total: `${(total / 100).toFixed(2)} ${currencyCode}`,
|
||||
}
|
||||
|
||||
await this.sendgridService_.transactionalEmail("order.placed", data)
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
})
|
||||
|
||||
this.eventBus_.subscribe("order.cancelled", async (order) => {
|
||||
|
||||
@@ -5,5 +5,7 @@ node_modules
|
||||
/*.js
|
||||
!index.js
|
||||
yarn.lock
|
||||
src
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "medusa-plugin-slack-notification",
|
||||
"version": "1.0.13",
|
||||
"version": "1.0.12-alpha.176+0646bd3",
|
||||
"description": "Slack notifications",
|
||||
"main": "index.js",
|
||||
"repository": {
|
||||
@@ -39,9 +39,9 @@
|
||||
"axios": "^0.19.2",
|
||||
"body-parser": "^1.19.0",
|
||||
"express": "^4.17.1",
|
||||
"medusa-core-utils": "^1.0.11",
|
||||
"medusa-test-utils": "^1.0.13",
|
||||
"medusa-core-utils": "^1.0.12-alpha.787+0646bd3",
|
||||
"medusa-test-utils": "^1.0.12-alpha.176+0646bd3",
|
||||
"moment": "^2.27.0"
|
||||
},
|
||||
"gitHead": "3cc7cbe5124cbcbb75f6e1435db4dcfaa2a60408"
|
||||
"gitHead": "0646bd395a6056657cb0aa93c13699c4a9dbbcdd"
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
import axios from "axios"
|
||||
import moment from "moment"
|
||||
import { BaseService } from "medusa-interfaces"
|
||||
|
||||
class SlackService extends BaseService {
|
||||
@@ -23,20 +22,48 @@ class SlackService extends BaseService {
|
||||
}
|
||||
|
||||
async orderNotification(orderId) {
|
||||
const order = await this.orderService_.retrieve(orderId)
|
||||
const order = await this.orderService_.retrieve(orderId, {
|
||||
select: [
|
||||
"shipping_total",
|
||||
"discount_total",
|
||||
"tax_total",
|
||||
"refunded_total",
|
||||
"gift_card_total",
|
||||
"subtotal",
|
||||
"total",
|
||||
],
|
||||
relations: [
|
||||
"customer",
|
||||
"billing_address",
|
||||
"shipping_address",
|
||||
"discounts",
|
||||
"shipping_methods",
|
||||
"payments",
|
||||
"fulfillments",
|
||||
"returns",
|
||||
"gift_cards",
|
||||
"gift_card_transactions",
|
||||
"swaps",
|
||||
"swaps.return_order",
|
||||
"swaps.payment",
|
||||
"swaps.shipping_methods",
|
||||
"swaps.shipping_address",
|
||||
"swaps.additional_items",
|
||||
"swaps.fulfillments",
|
||||
],
|
||||
})
|
||||
|
||||
const subtotal = await this.totalsService_.getSubtotal(order)
|
||||
const shippingTotal = await this.totalsService_.getShippingTotal(order)
|
||||
const taxTotal = await this.totalsService_.getTaxTotal(order)
|
||||
const discountTotal = await this.totalsService_.getDiscountTotal(order)
|
||||
const total = await this.totalsService_.getTotal(order)
|
||||
const { subtotal, tax_total, discount_total, shipping_total, total } = order
|
||||
|
||||
const currencyCode = order.currency_code.toUpperCase()
|
||||
const taxRate = order.tax_rate / 100
|
||||
|
||||
let blocks = [
|
||||
{
|
||||
type: "section",
|
||||
text: {
|
||||
type: "mrkdwn",
|
||||
text: `Order *<${this.options_.admin_orders_url}/${order._id}|#${order.display_id}>* has been processed.`,
|
||||
text: `Order *<${this.options_.admin_orders_url}/${order.id}|#${order.display_id}>* has been processed.`,
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -56,15 +83,17 @@ class SlackService extends BaseService {
|
||||
type: "section",
|
||||
text: {
|
||||
type: "mrkdwn",
|
||||
text: `*Subtotal*\t${subtotal.toFixed(2)} ${
|
||||
order.currency_code
|
||||
}\n*Shipping*\t${shippingTotal.toFixed(2)} ${
|
||||
order.currency_code
|
||||
}\n*Discount Total*\t${discountTotal.toFixed(2)} ${
|
||||
order.currency_code
|
||||
}\n*Tax*\t${taxTotal.toFixed(2)} ${
|
||||
order.currency_code
|
||||
}\n*Total*\t${total.toFixed(2)} ${order.currency_code}`,
|
||||
text: `*Subtotal*\t${(subtotal / 100).toFixed(
|
||||
2
|
||||
)} ${currencyCode}\n*Shipping*\t${(shipping_total / 100).toFixed(
|
||||
2
|
||||
)} ${currencyCode}\n*Discount Total*\t${(
|
||||
discount_total / 100
|
||||
).toFixed(2)} ${currencyCode}\n*Tax*\t${(tax_total / 100).toFixed(
|
||||
2
|
||||
)} ${currencyCode}\n*Total*\t${(total / 100).toFixed(
|
||||
2
|
||||
)} ${currencyCode}`,
|
||||
},
|
||||
},
|
||||
]
|
||||
@@ -74,7 +103,9 @@ class SlackService extends BaseService {
|
||||
type: "section",
|
||||
text: {
|
||||
type: "mrkdwn",
|
||||
text: `*Promo Code*\t${d.code} ${d.discount_rule.value}${d.discount_rule.type === "percentage" ? "%" : ` ${order.currency_code}`}`,
|
||||
text: `*Promo Code*\t${d.code} ${d.rule.value}${
|
||||
d.rule.type === "percentage" ? "%" : ` ${currencyCode}`
|
||||
}`,
|
||||
},
|
||||
})
|
||||
})
|
||||
@@ -88,12 +119,13 @@ class SlackService extends BaseService {
|
||||
type: "section",
|
||||
text: {
|
||||
type: "mrkdwn",
|
||||
text: `*${lineItem.title}*\n${lineItem.quantity} x ${
|
||||
!Array.isArray(lineItem.content) &&
|
||||
(lineItem.content.unit_price * (1 + order.tax_rate)).toFixed(2)
|
||||
} ${order.currency_code}`,
|
||||
text: `*${lineItem.title}*\n${lineItem.quantity} x ${(
|
||||
(lineItem.unit_price / 100) *
|
||||
(1 + taxRate)
|
||||
).toFixed(2)} ${currencyCode}`,
|
||||
},
|
||||
}
|
||||
|
||||
if (lineItem.thumbnail) {
|
||||
let url = lineItem.thumbnail
|
||||
if (
|
||||
@@ -106,9 +138,8 @@ class SlackService extends BaseService {
|
||||
line.accessory = {
|
||||
type: "image",
|
||||
alt_text: "Item",
|
||||
image_url: url
|
||||
image_url: url,
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
blocks.push(line)
|
||||
|
||||
@@ -4,8 +4,8 @@ class OrderSubscriber {
|
||||
|
||||
this.eventBus_ = eventBusService
|
||||
|
||||
this.eventBus_.subscribe("order.placed", async (order) => {
|
||||
await this.slackService_.orderNotification(order._id)
|
||||
this.eventBus_.subscribe("order.placed", async ({ id }) => {
|
||||
await this.slackService_.orderNotification(id)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
export default [
|
||||
"BE",
|
||||
"BG",
|
||||
"CZ",
|
||||
"DK",
|
||||
"DE",
|
||||
"EE",
|
||||
"IE",
|
||||
"EL",
|
||||
"ES",
|
||||
"FR",
|
||||
"HR",
|
||||
"IT",
|
||||
"CY",
|
||||
"LV",
|
||||
"LT",
|
||||
"LU",
|
||||
"HU",
|
||||
"MT",
|
||||
"NL",
|
||||
"AT",
|
||||
"PL",
|
||||
"PT",
|
||||
"RO",
|
||||
"SI",
|
||||
"SK",
|
||||
"FI",
|
||||
"SE",
|
||||
"UK",
|
||||
]
|
||||
@@ -1,8 +0,0 @@
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports["default"] = void 0;
|
||||
var _default = ["BE", "BG", "CZ", "DK", "DE", "EE", "IE", "EL", "ES", "FR", "HR", "IT", "CY", "LV", "LT", "LU", "HU", "MT", "NL", "AT", "PL", "PT", "RO", "SI", "SK", "FI", "SE", "UK"];
|
||||
exports["default"] = _default;
|
||||
@@ -1,7 +1,6 @@
|
||||
{
|
||||
"name": "medusa-plugin-twilio-sms",
|
||||
"version": "1.0.12",
|
||||
"description": "Twilio SMS service",
|
||||
"version": "1.0.11-alpha.176+0646bd3",
|
||||
"main": "index.js",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -36,9 +35,9 @@
|
||||
"dependencies": {
|
||||
"@babel/plugin-transform-classes": "^7.9.5",
|
||||
"body-parser": "^1.19.0",
|
||||
"medusa-core-utils": "^1.0.11",
|
||||
"medusa-test-utils": "^1.0.13",
|
||||
"medusa-core-utils": "^1.0.12-alpha.787+0646bd3",
|
||||
"medusa-test-utils": "^1.0.12-alpha.176+0646bd3",
|
||||
"twilio": "^3.49.1"
|
||||
},
|
||||
"gitHead": "e1c465d244b20e68dd88bcb12db0a061f25581ec"
|
||||
"gitHead": "0646bd395a6056657cb0aa93c13699c4a9dbbcdd"
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "medusa-plugin-wishlist",
|
||||
"version": "1.0.13",
|
||||
"version": "1.0.12-alpha.176+0646bd3",
|
||||
"description": "Provides /customers/:id/wishlist to add items to a customr's wishlist",
|
||||
"main": "index.js",
|
||||
"repository": {
|
||||
@@ -37,8 +37,8 @@
|
||||
"dependencies": {
|
||||
"body-parser": "^1.19.0",
|
||||
"express": "^4.17.1",
|
||||
"medusa-core-utils": "^1.0.11",
|
||||
"medusa-test-utils": "^1.0.13"
|
||||
"medusa-core-utils": "^1.0.12-alpha.787+0646bd3",
|
||||
"medusa-test-utils": "^1.0.12-alpha.176+0646bd3"
|
||||
},
|
||||
"gitHead": "3cc7cbe5124cbcbb75f6e1435db4dcfaa2a60408"
|
||||
"gitHead": "0646bd395a6056657cb0aa93c13699c4a9dbbcdd"
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "medusa-test-utils",
|
||||
"version": "1.0.13",
|
||||
"version": "1.0.12-alpha.176+0646bd3",
|
||||
"description": "Test utils for Medusa",
|
||||
"main": "dist/index.js",
|
||||
"repository": {
|
||||
@@ -29,8 +29,8 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/plugin-transform-classes": "^7.9.5",
|
||||
"medusa-core-utils": "^1.0.11",
|
||||
"mongoose": "^5.8.0"
|
||||
"medusa-core-utils": "^1.0.12-alpha.787+0646bd3",
|
||||
"randomatic": "^3.1.1"
|
||||
},
|
||||
"gitHead": "3cc7cbe5124cbcbb75f6e1435db4dcfaa2a60408"
|
||||
"gitHead": "0646bd395a6056657cb0aa93c13699c4a9dbbcdd"
|
||||
}
|
||||
@@ -1,23 +1,19 @@
|
||||
import mongoose from "mongoose"
|
||||
|
||||
String.prototype.equals = function(that) {
|
||||
return this === that
|
||||
}
|
||||
import randomize from "randomatic";
|
||||
|
||||
class IdMap {
|
||||
ids = {}
|
||||
ids = {};
|
||||
|
||||
getId(key, backend=false) {
|
||||
getId(key, prefix = "", length = 10) {
|
||||
if (this.ids[key]) {
|
||||
return this.ids[key]
|
||||
return this.ids[key];
|
||||
}
|
||||
|
||||
const mongooseId = `${mongoose.Types.ObjectId()}`
|
||||
this.ids[key] = mongooseId
|
||||
const id = `${prefix && prefix + "_"}${randomize("Aa0", length)}`;
|
||||
this.ids[key] = id;
|
||||
|
||||
return mongooseId
|
||||
return id;
|
||||
}
|
||||
}
|
||||
|
||||
const instance = new IdMap()
|
||||
export default instance
|
||||
const instance = new IdMap();
|
||||
export default instance;
|
||||
@@ -1 +1,3 @@
|
||||
export { default as IdMap } from "./id-map"
|
||||
export { default as IdMap } from "./id-map";
|
||||
export { default as MockRepository } from "./mock-repository";
|
||||
export { default as MockManager } from "./mock-manager";
|
||||
@@ -0,0 +1,13 @@
|
||||
export default {
|
||||
getCustomRepository: function (repo) {
|
||||
return repo;
|
||||
},
|
||||
|
||||
transaction: function (isolationOrCb, cb) {
|
||||
if (typeof isolationOrCb === "string") {
|
||||
return cb(this);
|
||||
} else {
|
||||
return isolationOrCb(this);
|
||||
}
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,67 @@
|
||||
export default ({
|
||||
create,
|
||||
update,
|
||||
remove,
|
||||
softRemove,
|
||||
find,
|
||||
findOne,
|
||||
findOneOrFail,
|
||||
save,
|
||||
} = {}) => {
|
||||
return {
|
||||
create: jest.fn().mockImplementation((...args) => {
|
||||
if (create) {
|
||||
return create(...args);
|
||||
}
|
||||
return {};
|
||||
}),
|
||||
softRemove: jest.fn().mockImplementation((...args) => {
|
||||
if (softRemove) {
|
||||
return softRemove(...args);
|
||||
}
|
||||
return {};
|
||||
}),
|
||||
remove: jest.fn().mockImplementation((...args) => {
|
||||
if (remove) {
|
||||
return remove(...args);
|
||||
}
|
||||
return {};
|
||||
}),
|
||||
update: jest.fn().mockImplementation((...args) => {
|
||||
if (update) {
|
||||
return update(...args);
|
||||
}
|
||||
}),
|
||||
findOneOrFail: jest.fn().mockImplementation((...args) => {
|
||||
if (findOneOrFail) {
|
||||
return findOneOrFail(...args);
|
||||
}
|
||||
}),
|
||||
findOne: jest.fn().mockImplementation((...args) => {
|
||||
if (findOne) {
|
||||
return findOne(...args);
|
||||
}
|
||||
}),
|
||||
findOneOrFail: jest.fn().mockImplementation((...args) => {
|
||||
if (findOneOrFail) {
|
||||
return findOneOrFail(...args);
|
||||
}
|
||||
}),
|
||||
find: jest.fn().mockImplementation((...args) => {
|
||||
if (find) {
|
||||
return find(...args);
|
||||
}
|
||||
}),
|
||||
softRemove: jest.fn().mockImplementation((...args) => {
|
||||
if (softRemove) {
|
||||
return softRemove(...args);
|
||||
}
|
||||
}),
|
||||
save: jest.fn().mockImplementation((...args) => {
|
||||
if (save) {
|
||||
return save(...args);
|
||||
}
|
||||
return Promise.resolve(...args);
|
||||
}),
|
||||
};
|
||||
};
|
||||
Loaded 100 of 499 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user