DataONEorg / bookkeeper

Bookkeeper keeps track of DataONE product subscriptions and quotas for researchers using the extended services.
Other
1 stars 2 forks source link

integrate payments via bluefin payconex #93

Open mbjones opened 1 year ago

mbjones commented 1 year ago

We have a merchant account through Bluefin Payconex (see payconex developer docs) and need to integrate that in with bookkeeper. See the overview of the process from the dev docs:

image

The basic flow would be that:

  1. User picks products and quantity in browser and clicks Checkout
  2. payconex iframe is generated to collect user credit card data
  3. payconex iframe sends that data to payconex, which returns an eToken
  4. js code in browser app sends the eToken to bookkeeper
  5. bookkeeper sends a QSAPI request to payconex with the etoken
  6. payconex responds to bookkeeper with approve/decline
  7. bookkeeper updates the database with the payment status and returns status to browser client
mbjones commented 1 year ago

Initial thoughts on implementation sequence using Payconex's HostedForm utility that embeds an iFrame for encrypted credit card details.

%%{init: {
  "theme": "forest",
  "sequence": { "showSequenceNumbers": true }
}}%%
sequenceDiagram
    participant Bookkeeper
    participant Purser
    participant Payconex

    note left of Purser: Set up products form
    Purser->>+Bookkeeper: GET /bookkeeper/v1/products
    Bookkeeper-->>-Purser: productList
    Purser->>Purser: showProductForm()

    rect rgb(200, 223, 255)
    note right of Purser: Create order and set up payment form
    Purser->>Purser: checkout()
    Purser->>+Bookkeeper: POST /bookkeeper/v1/order
    Bookkeeper->>Bookkeeper: storeOrder()
    Bookkeeper-->>-Purser: orderId

    Purser->>+Payconex: update_iframe(orderid, productlist, amount, user details)
    Payconex-->>-Purser: iFrame
    end

    rect rgb(170, 223, 255)
    note right of Bookkeeper: Process payment and notify Bookkeeper
    Purser->>+Payconex: pay(orderid, productlist, amount)
    Payconex->>Purser: receipt

    Payconex->>-Bookkeeper: POST /bookkeeper/v1/order/{orderid}/pay OrderStatus
    activate Bookkeeper
    Bookkeeper->>Bookkeeper: updateOrder()
    deactivate Bookkeeper
    end

    Purser->>+Bookkeeper: GET /bookkeeper/v1/order/{orderid}
    Bookkeeper-->>-Purser: order
mbjones commented 1 year ago

A state diagram for order transitions. States from the SQL table definition comments.

stateDiagram-v2
    direction LR
    [*] --> Created
    Created --> Canceled
    Created --> Paid
    Paid --> Returned
    Paid --> Fulfilled
    Canceled --> [*]
    Fulfilled --> [*]
    Returned --> [*]

Alternative with states from the Order.java representation class. I don't see any way to make these make total sense.

active|created|paid|past_due|refunded|trialing|unpaid

stateDiagram-v2
    direction LR
    [*] --> Created
    Created --> PastDue
    Created --> Unpaid
    Created --> Paid
    Created --> Trialing
    Paid --> Refunded
    Paid --> Active
    Trialing --> Active
    Active --> [*]
    Refunded --> [*]

Alternative as a hybrid of these to be evaluated:

active|created|paid|expired|refunded|trialing

stateDiagram-v2
direction LR
[*] --> Created
Created --> Paid
Created --> Trialing
Trialing --> Paid
Paid --> Paid
Trialing --> Expired
Created --> Canceled
Paid --> Refunded
Paid --> Expired
Canceled --> [*]
Expired --> [*]
Refunded --> [*]
mbjones commented 1 year ago

Initial design of PaymentsResource implemented in SHA dcdd9e80587 and SHA 523034a52237. The new REST service lives at the endpoint /bookkeeper/v1/payments, and the only implemented method is currently a POST which enables creation of a new payment transaction. This transaction takes the form of a postback transaction log from the transaction processor (in this case Bluefin Payconex) which has the following format:

Payment Transaction JSON format ```json-ld { "account_id": "351818369912", "timestamp": 1374346390, "count": 1, "hash": "c3a0b8cddacab5c761fd61d9176013e9287384938b0c2362b6b4bc090a257c6a", "responses": [ { "transaction_id": "000000005351", "tender_type": "CARD", "transaction_timestamp": "2022-04-27 18:40:16", "card_brand": "VISA", "transaction_type": "SALE", "last4": "1111", "card_expiration": "0326", "authorization_code": "OK2826", "authorization_message": "APPROVED", "request_amount": "575.04", "transaction_amount": "575.04", "fid": "31721", "ip_address": "24.237.6.75", "first_name": "Matthew", "last_name": "Jones", "keyed": "1", "swiped": "0", "entry_mode": "keyed", "transaction_approved": "1", "avs_response": "Y", "reason_code": "000", "trace_number": "000000006901", "network": "VISA", "currency": "USD", "error": "", "error_code": "0", "error_message": "", "error_msg": "", "orderid": "33924", "products": "DataONEPlus(1);", "first_last_name": "", "g-recaptcha-response": "03AGdBq26JR5c-GBYAZKrejJwJX4FFaHejaOmEFnJVrpT33WL3N8hq9VBVbCzaIwzSGUYrcAZiJ9LT_U_-GGxgo5kQS8AJS4YFhcsEdbpePloX1VdA1HGDnNaqEsdqD12doWwvdrVFy15l6u8tPBzjm07eSGoh4qGBBAmo5nxx0CdATHknnX55dCdaNzZnXI6bQyzvTFAAxoJ1otlyvQb9gWgz4PrrPVju7FwCPPOtNSL2rG9skhspfi5SaUo2-paM-LIYlpUb0hJT8JVHfLbYBA8GFNhZs1d5IlKeGLbsen2UQQAKd3cYt_H5Q5hFtTUjlx_tPIUkArc2xad-edd8IFEbYaQIpATXf8suVSaKBt5QFqdeI3VBGZaX8CUMVBNC3io3NaVX0kx-bcluAfHe6k9PE4u9YztUtlXMHfw-B92Q5xc6_4OWZ_5yRSG93Lja4oORxS7cNnrn", "hiddenRecaptcha": "", "hosted_payment_signal": "1", "callback_method": "GET", "expressCheckOutPayload": "", "description": "Amount" } ] } ```

Still much to do, including:

mbjones commented 1 year ago

Revised diagram on implementation sequence using Payconex's HostedForm utility that embeds an iFrame for encrypted credit card details, with the new approach of using postback transaction receipts to finalize the transaction on the server side:

%%{init: {
  "theme": "forest",
  "sequence": { "showSequenceNumbers": true }
}}%%
sequenceDiagram
    participant Bookkeeper
    participant Purser
    participant Payconex

    note left of Purser: Set up products form
    Purser->>+Bookkeeper: GET /bookkeeper/v1/products
    Bookkeeper-->>-Purser: productList
    Purser->>Purser: showProductForm()

    rect rgb(200, 223, 255)
    note right of Purser: Create order and set up payment form
    Purser->>Purser: checkout()
    Purser->>+Bookkeeper: POST /bookkeeper/v1/order
    Bookkeeper->>Bookkeeper: storeOrder()
    Bookkeeper-->>-Purser: orderId

    Purser->>+Payconex: update_iframe(orderid, productlist, amount, user details)
    Payconex-->>-Purser: iFrame
    end

    rect rgb(170, 223, 255)
    note right of Bookkeeper: Process payment and notify Bookkeeper
    Purser->>+Payconex: pay(orderid, productlist, amount)
    Payconex->>Purser: receipt

    Payconex->>-Bookkeeper: POST /bookkeeper/v1/payments Payment
    activate Bookkeeper
    Bookkeeper->>Bookkeeper: validateTransaction()
    Bookkeeper->>Bookkeeper: processPayment()
    Bookkeeper->>Payconex: HTTP 200 or Error
    deactivate Bookkeeper
    end

    Purser->>+Bookkeeper: GET /bookkeeper/v1/order/{orderid}
    Bookkeeper-->>-Purser: order
    note right of Purser: Update UI based<br> on order status