Microsoft Dynamics integration — the flow from prepare to callback

Dynamics does not print: it prepares the cheque by API and receives a link a logged-in employee opens to print; on completion the row is created and the result plus image go back to Dynamics. Eight steps, three endpoints, three duplicate guards.

Last updated: 2026-09-21

On this page

The idea

The app never prints on anyone’s behalf — printing is an employee’s action from their browser on their printer. So the integration has two halves: an API call that prepares, and an employee session that prints.

شفرة
[Dynamics] ──(1) POST /api/cheque-log/dynamics  with API key──▶ [Cheques] prepares, caches a draft (1 h)
[Dynamics] ◀──(2) launch_url + launch_token ───────────────────┘
    │ (3) shows the link to the employee, or redirects them
    ▼
[Employee browser — logged in]
    (4) opens /bank/{id}/template/{id}?dynamics_launch=TOKEN
    (5) GET  …/launch/{token}           loads the draft into the print screen
    (6) prints from the normal screen
    (7) POST …/launch/{token}/complete  after the print dialog closes ⟹ row created, image stored
[Dynamics] ◀──(8) callback HTTP POST «cheque.printed» ─── [Cheques]

Step 1 creates no cheque — no row until step 7. Steps 5 and 7 are browser session calls, not callable from a server.

Step 1 — prepare: POST /api/cheque-log/dynamics

(Alias POST /api/cheque-logs/dynamics.) Fields and their map in Fields & template.

bash
curl -s -X POST "APP_URL/api/cheque-log/dynamics" \
  -H "X-API-KEY: KEY" -H "Content-Type: application/json" -H "Accept: application/json" \
  -d '{
        "external_id": "DYN-1001",
        "bank_id": 2,
        "payee_name": "شركة النيل للتوريدات",
        "net_amount": "1250.50",
        "currency_code": "EGP",
        "issue_date": "2026-10-15",
        "document_no": "PV-1001",
        "memo": "دفعة مورّد"
      }'

200 (or 302 in redirect mode redirect: true):

json
{
  "success": true,
  "message": "تم تجهيز الشيك للمعاينة والطباعة",
  "action": "prepared",
  "launch_token": "<48 chars>",
  "save_mode": "after_print",
  "data": { "bank_name": "بنك مصر", "template_name": "بنك مصر - نموذج 1", "recipient_name": "…",
            "amount": "1250.50", "external_source": "microsoft_dynamics", "external_id": "DYN-1001",
            "request_hash": "…", "template_selection_required": false, "available_templates": [] },
  "preview_url": "APP_URL/bank/2/template/5?dynamics_launch=…",
  "launch_url": "…", "redirect_url": "…", "print_url": "…"
}

The four URLs are one value under four names. Keep launch_token and hand launch_url to the employee. The draft lives one hour (DYNAMICS_LAUNCH_TTL_SECONDS).

template_selection_required: true = the bank resolved but the template did not (several active templates without a single featured one): the link becomes /bank/{id}?dynamics_launch=… and the employee picks the template on screen — available_templates lists the options.

Steps 5 and 7 — the employee session

Endpoint Guard Response
GET /api/cheque-log/dynamics/launch/{token} session + active company 200 with the draft (data + resolved_bank_id + resolved_template_id) · 404 {"message":"Dynamics launch draft not found or expired."}
POST /api/cheque-log/dynamics/launch/{token}/complete session + print-batch permission 201 created · 200 action: "reused" on repeat

The screen calls both itself; what matters to you: completion is the moment of creation. The completion body overrides the draft field by field — two traps to know:

تنبيه

  1. save_cheque_image defaults to false at completion: image_base64 is required but not stored unless the flag is sent as true. The image is sent to the callback either way.
  2. liner_enabled and two_lines override the draft: absent at completion = false — a cheque prepared with the liner on and completed without re-sending it is stored without a liner.

The stored image carries a “preview copy only” watermark. The row is stamped printed_at = now() · printed_by = the employee · initial status from the direction · external_source · external_id · request_hash from the draft, not the request.

Step 8 — the callback

After creation, one HTTP POST to DYNAMICS_PRINT_CALLBACK_URL with event cheque.printed, the cheque data and the full base64 image. One synchronous attempt, no retry (retried only if completion is called again and the previous attempt was not sent). Its result appears in the completion response under dynamics_callback: sent · failed · skipped (no URL configured).

What the employee sees: “saved and print status sent to Dynamics” · “saved locally but not sent because the link settings are incomplete” · “saved locally but the image and print status could not be sent”.

Against duplicates — three layers

  1. Same token twice: the draft holds the created row ⟹ 200 reused and the row unchanged (validation and template resolution skipped).
  2. Match by external key: external_source + external_id (or request_hash when absent) under a row lock — a printed row is returned untouched and the new payload ignored.
  3. Database constraint: UNIQUE(external_source, external_id).

request_hash is computed from 17 fields (bank, template, direction, date, payee, amount, currency, number, liner…) and excludes stub fields and account/book/partner IDs. Always send external_id.

Company

Decided at prepare from the key’s company and stored in the draft; completion reads it from there and does not accept a change from the request body. A prepare payload that contradicts it (company_id or a reference from another company) ⟹ 422. Updating an existing row of a different company: 422 السجل ده مسجّل لشركة تانية. A final row (damaged · cancelled · reissued) refuses updates with its own message.

Setup

In the app — the Dynamics tab of the Integration screen, and environment variables on the server:

Variable Default Effect
DYNAMICS_PRINT_CALLBACK_URL empty empty ⟹ no call, status skipped
DYNAMICS_PRINT_CALLBACK_AUTH_HEADER Authorization sent only with a value
DYNAMICS_PRINT_CALLBACK_AUTH_VALUE name and value together are the condition
DYNAMICS_PRINT_CALLBACK_TIMEOUT_SECONDS 15 call timeout
DYNAMICS_PRINT_CALLBACK_VERIFY_SSL true false disables certificate checks — never in production
DYNAMICS_SOURCE microsoft_dynamics the stamped external_source
DYNAMICS_LAUNCH_TTL_SECONDS 3600 launch token lifetime

In Dynamics: receive launch_url and show it to an employee logged into Cheques Egypt, and expose an endpoint that accepts the callback and reads event: "cheque.printed" — over HTTPS, because the payload carries the cheque image.

اطلب نسختك التجريبية مجانًا