diff --git a/README.md b/README.md index 20bb2ee..9a6c47b 100644 --- a/README.md +++ b/README.md @@ -6,3 +6,12 @@ An interactive version of the documentation is hosted on https://developer.kivra ## Deployment This project is deployed automatically when a commit is pushed to `master`. +Feature branches are also deployed to `https://developer.kivra.com/branch-name`. + +## Development + +Preview changes to `swagger.yml` using docker-compose: + +```bash +$ docker-compose up +``` diff --git a/cloudbuild.yaml b/cloudbuild.yaml index df701b9..5d3fddf 100644 --- a/cloudbuild.yaml +++ b/cloudbuild.yaml @@ -1,3 +1,11 @@ steps: - name: 'gcr.io/cloud-builders/gsutil' - args: ['cp', 'index.html', 'swagger.yml', 'gs://developer.kivra.com'] + entrypoint: 'bash' + args: + - '-c' + - '[[ "$BRANCH_NAME" != "master" ]] && gsutil cp index.html swagger.yml "gs://developer.kivra.com/$BRANCH_NAME/" || echo "skipping . . ."' + - name: 'gcr.io/cloud-builders/gsutil' + entrypoint: 'bash' + args: + - '-c' + - '[[ "$BRANCH_NAME" == "master" ]] && gsutil cp index.html swagger.yml "gs://developer.kivra.com/" || echo "skipping . . ."' diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..eb01fb7 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,8 @@ +version: "3.8" +services: + web: + image: nginx:stable-alpine + volumes: + - .:/usr/share/nginx/html + ports: + - "1337:80" \ No newline at end of file diff --git a/swagger.yml b/swagger.yml index 9ac2806..588d1b3 100644 --- a/swagger.yml +++ b/swagger.yml @@ -1151,6 +1151,167 @@ paths: items: $ref: '#/components/schemas/CompanyList' +# ############################################## +# POST /v2/tenant/TKEY/content +# ############################################## + /v2/tenant/{tenantKey}/content: + post: + tags: + - "Tenant API - Content" + summary: Send content v2 to a recipient (user or company) + operationId: Send content v2 + description: | + Metadata is data that Kivra needs to send the Content to the right User. It may also determine how a User can interact with the Content. + + ### Minimum Metadata + As a minimum a valid `ssn` or `vat_number` is required. + + + + ### Retained Content + A retained Content is a Content that is sent to a Recipient who is not yet a user of Kivra. Once that Recipient register with Kivra, the Content will be delivered to that Recipient’s Kivra account. Retained Content has a time limit for how long they can be retained before being deleted, ie removed unless the Recipient registers within a given period of time. + + + + Sending retained Content uses all the same attributes as a normal Content with the difference for some additional metadata-attributes. + By enabling the `retain` metadata attribute and setting it to `true` will enable possible retention of a Content. Kivra’s logic is to first look if the Recipient exist. If it does Kivra will deliver the Content as usual, if the Recipient doesn’t exist Kivra will retain the Content for the default amount of time (*30 days*). This makes it easy for an Integrator to issue a "retain or deliver"-logic for all it’s Contents. + + If an integrator want to retain a Content for a another time-period than Kivra’s default if can be done via the `retention_time` additional metadata. + + ### Duplicate control + If exactly the same payload (content plus metadata, with only exception being the `generated_at` field) is received more than once, only the first occurrence will result in a delivery. In the other occurrences an OK message will be returned, but no corresponding content will be delivered to the receiver. This is a security mechanism to allow senders to safely re-send the same payload in case there is uncertainty on whether the previous sending resulted in a delivery or not. A typical example is in case of timeout error, where the sender cannot establish whether the sending resulted in a content delivery or not. + + + + It's important to underline that the duplicate control is made by checking the complete payload (beside `generated_at`), not only the attached content. This means that any change in the payload will cause the duplicate check to fail and the corresponding content to be delivered. + + Another important consequence is that in case a sender wants to safely re-send a PDF content, it is important that the PDF is not generated again between the API calls as this almost certainly results in a slightly different PDF and therefore in a different payload, meaning that the duplication check will not be able to recognise it as a duplicate. + + ### Responsive Content Lifecycle + When sending html content it's required to provide a PDF version of the document as the base `Part`. Inside this base `Part` you can then assign the responsive html version of the document as a `ResponsivePart` under the key: `responsive_part`. + security: + - oAuth2Client: + - 'post:kivra.v2.tenant.{tenantKey}.content' + parameters: + - name: tenantKey + in: path + description: The unique Key for a Tenant + required: true + schema: + type: string + format: hexadecimal + responses: + 201: + description: | + Content Created succesfully + content: + application/json: + schema: + oneOf: + - $ref: '#/components/schemas/Content_user_v2' + - $ref: '#/components/schemas/Content_company_v2' + headers: + kivra-objkey: + description: Object Key + schema: + type: string + format: 'hexadecimal value' + kivra-retained: + description: | + Boolean denoting if a Content was Retained, Note: this header is **only** returned when a Content is retained + schema: + type: boolean + location: + description: URL to created Object + schema: + type: string + format: url + requestBody: + content: + application/json: + schema: + oneOf: + - $ref: '#/components/schemas/Content_user_v2' + - $ref: '#/components/schemas/Content_company_v2' + examples: + responsive content to user: + value: + ssn: '191212121212' + subject: Sample Responsive Content to User + generated_at: '2020-05-25' + retain: false + tenant_info: string + parts: + - name: filename.pdf + content_type: application/pdf + data: JVBERi0xLjEKJcKlwrHDqwoKMSAwIG9iagogIDw8IC9UeXBlIC9DYXRhbG9nCiAgICAgL1BhZ2VzIDIgMCBSCiAgPj4KZW5kb2JqCgoyIDAgb2JqCiAgPDwgL1R5cGUgL1BhZ2VzCiAgICAgL0tpZHMgWzMgMCBSXQogICAgIC9Db3VudCAxCiAgICAgL01lZGlhQm94IFswIDAgMzAwIDE0NF0KICA+PgplbmRvYmoKCjMgMCBvYmoKICA8PCAgL1R5cGUgL1BhZ2UKICAgICAgL1BhcmVudCAyIDAgUgogICAgICAvUmVzb3VyY2VzCiAgICAgICA8PCAvRm9udAogICAgICAgICAgIDw8IC9GMQogICAgICAgICAgICAgICA8PCAvVHlwZSAvRm9udAogICAgICAgICAgICAgICAgICAvU3VidHlwZSAvVHlwZTEKICAgICAgICAgICAgICAgICAgL0Jhc2VGb250IC9UaW1lcy1Sb21hbgogICAgICAgICAgICAgICA+PgogICAgICAgICAgID4+CiAgICAgICA+PgogICAgICAvQ29udGVudHMgNCAwIFIKICA+PgplbmRvYmoKCjQgMCBvYmoKICA8PCAvTGVuZ3RoIDU1ID4+CnN0cmVhbQogIEJUCiAgICAvRjEgMTggVGYKICAgIDAgMCBUZAogICAgKEhlbGxvIFdvcmxkKSBUagogIEVUCmVuZHN0cmVhbQplbmRvYmoKCnhyZWYKMCA1CjAwMDAwMDAwMDAgNjU1MzUgZiAKMDAwMDAwMDAxOCAwMDAwMCBuIAowMDAwMDAwMDc3IDAwMDAwIG4gCjAwMDAwMDAxNzggMDAwMDAgbiAKMDAwMDAwMDQ1NyAwMDAwMCBuIAp0cmFpbGVyCiAgPDwgIC9Sb290IDEgMCBSCiAgICAgIC9TaXplIDUKICA+PgpzdGFydHhyZWYKNTY1CiUlRU9GCg== + responsive_part: + name: filename.html + content_type: text/html + data: PCFkb2N0eXBlIGh0bWw+CjxoZWFkPgogIDxtZXRhIGNoYXJzZXQ94oCddXRmLTjigJ0+CiAgPHRpdGxlPlRlc3Q8L3RpdGxlPgo8L2hlYWQ+Cjxib2R5PgogIDxwPlRoaXMgaXMgb25seSBhIHRlc3QuPC9wPgo8L2JvZHk+ + invoice to user: + value: + ssn: '191212121212' + subject: Sample Invoice to User + generated_at: '2020-05-25' + retain: false + tenant_info: string + parts: + - name: filename.pdf + content_type: application/pdf + data: REVBREJFRUY= + payment: + payable: true + status: unpaid + currency: SEK + due_date: '2021-01-01' + total_owed: '123.50' + type: SE_OCR + method: '1' + account: '12345' + reference: 123OCRNUMBER456 + variable_amount: false + min_amount: '50.00' + content to user: + value: + ssn: '191212121212' + subject: Sample Content to User + generated_at: '2020-05-25' + retain: false + tenant_info: string + parts: + - name: filename.pdf + content_type: application/pdf + data: REVBREJFRUY= + payslip to user: + value: + ssn: '191212121212' + subject: Sample Payslip to User + generated_at: '2020-05-25' + retain: true + retention_time: '390' + tenant_info: string + parts: + - name: filename.pdf + content_type: application/pdf + data: REVBREJFRUY= + content to company: + value: + vat_number: SE556840226601 + subject: Sample Content to Company + generated_at: '2020-05-25' + tenant_info: string + parts: + - name: filename.pdf + content_type: application/pdf + data: REVBREJFRUY= + # ############################################## # POST /v1/tenant/TKEY/content # ############################################## @@ -1704,6 +1865,101 @@ paths: components: schemas: +# ############################################## +# SCHEMA Content_user_v2 +# ############################################## + Content_user_v2: + type: object + required: + - ssn + properties: + ssn: + description: | + User's unique SSN, format: `YYYYMMDDnnnn` + type: string + writeOnly: true + example: "191212121212" + subject: + description: This Subject/Title will be visibile in the Recipients Inbox. + type: string + example: "Sample Invoice" + generated_at: + type: string + format: ISO8601 + example: "2016-12-12" + description: | + Optional attribute which denotes when a specific Content was generated at the tenant/integrator’s site. The attribute will be used for sorting in the Kivra user interface, which makes it possible for a tenant or integrator to control the sorting. + retain: + description: | + Boolean denoting if Kivra should try and retain this Content if it can’t be delivered. Default `false`. Please note that retain must never be set to `true` for payable content. + type: boolean + writeOnly: true + readOnly: true + example: false + retention_time: + description: | + How long to retain a Content. Supported values: `"30"` and `"390"` + type: string + writeOnly: true + readOnly: true + example: "30" + enum: ["30","390"] + tenant_info: + description: | + An arbitrary string defined by the tenant, used to group content for administrative tasks + type: string + writeOnly: true + readOnly: true + parts: + description: Array of file Objects + type: array + writeOnly: true + items: + oneOf: + - $ref: '#/components/schemas/Part' + payment: + $ref: '#/components/schemas/Payment' + +# ############################################## +# SCHEMA Content_company_v2 +# ############################################## + Content_company_v2: + type: object + required: + - vat_number + properties: + vat_number: + description: | + A valid VAT-identifier, Swedish format: `SE[xxxxxxxxxx]01` + type: string + readOnly: true + writeOnly: true + example: SE556840226601 + subject: + description: This Subject/Title will be visibile in the Recipients Inbox. + type: string + example: "Sample Invoice" + generated_at: + type: string + format: ISO8601 + example: "2016-12-12" + description: | + Optional attribute which denotes when a specific Content was generated at the tenant/integrator’s site. The attribute will be used for sorting in the Kivra user interface, which makes it possible for a tenant or integrator to control the sorting. + tenant_info: + description: | + An arbitrary string defined by the tenant, used to group content for administrative tasks + type: string + writeOnly: true + readOnly: true + parts: + description: Array of file Objects + type: array + writeOnly: true + items: + $ref: '#/components/schemas/Part' + payment: + $ref: '#/components/schemas/Payment' + # ############################################## # SCHEMA Content_user # ############################################## @@ -2255,6 +2511,61 @@ components: type: string example: "SE556840226601" +# ############################################## +# SCHEMA Responsive Part +# ############################################## + ResponsivePart: + type: object + required: + - name + - data + - content_type + properties: + name: + description: Arbritrary file-name that is shown alongside the File in the Kivra GUI + type: string + example: filename.html + data: + description: Base64-encoded data + type: string + format: 'Base64-encoded data' + example: REVBREJFRUY= + content_type: + description: The [Iana](http://www.iana.org/assignments/media-types/media-types.xhtml) Media type corresponding to the file, e.g. "text/html" + type: string + example: "text/html" + expires_at: + readOnly: true + description: Date and time after which the responsive part no longer will be recommended for default presentation. + type: string + example: "2020-08-22T01:30:00Z" + +# ############################################## +# SCHEMA Part +# ############################################## + Part: + type: object + required: + - name + - data + - content_type + properties: + name: + description: Arbritrary file-name that is shown alongside the File in the Kivra GUI + type: string + example: filename.pdf + data: + description: Base64-encoded data + type: string + format: 'Base64-encoded data' + example: REVBREJFRUY= + content_type: + description: The [Iana](http://www.iana.org/assignments/media-types/media-types.xhtml) Media type corresponding to the file, e.g. "application/pdf" + type: string + example: "application/pdf" + responsive_part: + $ref: '#/components/schemas/ResponsivePart' + # ############################################## # SCHEMA File # ##############################################