This is the repository of the specification and JSON-LD namespace for an extension for the Open Booking API to handle Stripe integrated payments.
Status: Draft | Please Provide Feedback via GitHub
This specification and namespace is in a draft state.
This specification includes changes to C2 and B endpoints necessary to integrate the Stripe Payment Intents API with the Open Booking API.
It is based on the Integrated Payments example in the Open Booking API specification, and Stripe's own best practices.
Please note:
- As per the Open Booking API specification, this extension should be implemented as an optional feature for Brokers that require it. Full conformance to the Open Booking API specification cannot be reached by a Booking System if all Brokers MUST use the extension to interact with the API. This is because enforcing a single payment provider severely limits the available use cases and business models within the OpenActive ecosystem.
- This extension cannot be integrated into the Open Booking API specification itself as it is vendor-specific, and hence it is not appropriate within the core of an open standard.
Additionally note that Payment reconciliation detail validation cannot be used in conjunction with this Stripe extension, as reconciliation is handled entirely by the Booking System in this case.
The sequence diagram below outlines the changes to the Simple Booking Flow when the Stripe Extension is in use:
sequenceDiagram
actor P as Customer
participant C as Broker
participant I as Booking System
participant S as Stripe
P ->> C: Confirm selection of<br />Opportunity/Offer
C ->> I: C2
I ->> I: [Check Availability, Optionally lease, etc]
I ->> S: Create Payment Intent<br />with expected payment amount
S -->> I: Payment Intent Client Secret
I -->> C: C2 Response, with:<br />-Publishable Stripe Key<br />- Seller Stripe Account ID <br />- Payment Intent Client Secret
C ->> C: Set-up Stripe Elements<br />using received Stripe details
P ->> C: Enters card details<br />and clicks pay
C ->> S: stripe.confirmPayment(..)
opt If 3D Secure required
S ->> C: Automatically prompt customer<br />with verification modal
C -->> S: Customer completes verification
end
alt If Payment Hold and Verification Successful
S ->> S: Put hold on payment
S ->> P: Redirect to Client's "Payment Processing" page<br />(return_url)<br />URL query contains Payment Intent ID
P -->> C: "Payment Processing"<br />page loads
C ->> I: (on page load) call B<br />with Payment Intent ID
I ->> I: [Make booking]
I ->> S: Capture PaymentIntent
S -->> I:
I -->> C: B response
C ->> C: [Perform post-booking actions]
end
The namespace MUST be referenced using the URL "https://openactive.io/stripe-extension"
(which will return the JSON-LD definition if the Accept
header contains application/ld+json
), and is designed to be used in conjunction with the "https://openactive.io/"
namespace.
When this extension is in use, the "@context"
of all requests and responses to the Open Booking API should include the namespace reference as follows:
{
"@context": [
"https://openactive.io/",
"https://openactive.io/stripe-extension"
],
"@type": "Order",
...
}
Note as above, in order to conform to the Open Booking API, a Booking System MUST respond to standard Open Booking API requests (that do not include this additional namespace in the "@context"
) as per the Open Booking API specification.
The Booking System MUST process requests as defined in this extension, only if stripe:paymentPageUrl
is present inside a stripe:PaymentIntent
within the payment
property of a C2, B, or P call:
"payment": {
"@type": "stripe:PaymentIntent",
"stripe:paymentPageUrl": "https://example.com/checkout"
}
If the payment
is of any other @type
, the Booking System MUST treat the request as if Stripe extension is not implemented.
The Stripe Extension MUST be used consistently (or not) for a given Order UUID.
The Broker uses a "stripe:PaymentIntent"
within payment
to indicate an intention to use Stripe for integrated payments, as it would with Payment reconciliation detail validation. The Broker MUST specify the web page or app on which Stripe will be used. This allows the booking system to authorise and track the usage of Stripe, which will aid PCI-DSS compliance.
"payment": {
"@type": "stripe:PaymentIntent",
"stripe:paymentPageUrl": "https://example.com/checkout"
}
Note that some Broker use cases will capture the customer
details on the same page as the card details, and the Stripe Extension MUST be used consistently (or not) for the same Order UUID, hence the above MUST be included in both C1 and C2.
The C1 and C2 responses each provide enough information for the Broker to authorise the Payment Intent (invoking 3D Secure as necessary). They also reflect back the provided payment
data.
"stripe:paymentRequest": {
"@type": "stripe:PaymentIntent",
"identifier": "pi_1GPsnyKarmweGdVC5WhNworN", // payment intent ID
"stripe:clientSecret": "pi_1GPsnyKarmweGdVC5WhNworN_secret_LgKKeZNVv4XlayiahHTt3YjHA",
"stripe:publishableKey": "pk_test_4JnvQX1ZfhZacZh3ZiLOrAXq",
"stripe:connectAccountIdentifier": "acct_24BFMpJ1svR5A89k"
},
"payment": {
"@type": "stripe:PaymentIntent",
"stripe:paymentPageUrl": "https://example.com/checkout"
}
The contents of stripe:paymentRequest
MUST be identical in both C1 and C2 responses.
stripe:paymentRequest
MUST only be present in C1 and C2 responses when totalPaymentDue
contains a non-zero price
.
For the Approval Flow, where Proposal Amendment is supported, a new stripe:paymentRequest
may be present in the revised OrderProposal
in the Order Proposals feed. This is only present in the case that the totalPaymentDue
has increased since P, and where incremental authorization has failed.
"stripe:paymentRequest": {
"@type": "stripe:PaymentIntent",
"identifier": "pi_1GPsnyKarmweGdVC5WhNworN", // new payment intent ID
"stripe:clientSecret": "pi_1GPsnyKarmweGdVC5WhNworN_secret_LgKKeZNVv4XlayiahHTt3YjHA",
"stripe:publishableKey": "pk_test_4JnvQX1ZfhZacZh3ZiLOrAXq",
"stripe:connectAccountIdentifier": "acct_24BFMpJ1svR5A89k"
}
The Broker MUST include the Payment Intent identifier in the request, and the Booking System MUST reflect it back in the response. This helps the Booking System to verify that the correct Payment Intent has been used by the Broker to accept a payment for this Order.
For the Approval Flow, payment
is only required at B in the case that a new stripe:paymentRequest
was present in the revised OrderProposal
in the Order Proposals feed (which is only possible if Proposal Amendment has been implemented by the Booking System).
Note that if "@type": "stripe:PaymentIntent"
is included in the B/P request, then C1 or C2 MUST have been called previously to retrieve a stripe:paymentRequest
.
In keeping with the specification, payment
MUST NOT be present where the totalPaymentDue
at C2 is of zero price
.
"payment": {
"@type": "stripe:PaymentIntent",
"stripe:paymentPageUrl": "https://example.com/checkout",
"identifier": "pi_1GPsnyKarmweGdVC5WhNworN" // payment intent ID
}
"payment": {
"@type": "stripe:PaymentIntent",
"stripe:paymentPageUrl": "https://example.com/checkout",
"identifier": "pi_1GPsnyKarmweGdVC5WhNworN" // payment intent ID
}
A pass-through approach for errors from the Stripe API ensures that the Broker has as much information as possible about any errors that occur, and can display messages to the end-user as appropriate.
A number of oa:OpenBookingError
subclasses are defined to identify the problem type of various error conditions. These are based on Stripe's own error types, and pass through any errors received from Stripe. All other errors defined here subclass stripe:Error
, which subclasses oa:OpenBookingError
.
Error Type (@type ) |
Status Code (oa:statusCode ) |
Use Case (schema:name ) |
---|---|---|
stripe:Error |
400 | A problem with Stripe was encountered that did not have a known error type |
stripe:ApiError |
400 | API error from Stripe's servers |
stripe:CardError |
400 | The user has provided a card that cannot be charged |
stripe:IdempotencyError |
409 | An Idempotency-Key was re-used on a request to Stripe that did not match the first request's API endpoint and parameters |
stripe:InvalidRequestError |
400 | Request to Stripe has invalid parameters |
stripe:MissingPaymentIntentDetailsError |
400 | stripe:PaymentIntent details identifier and stripe:paymentPageUrl were expected at B/P, but were not set. |
stripe:PaymentIntentContainsExcessivePropertiesError |
400 | stripe:PaymentIntent MUST NOT contain identifier at C1 or C2. |
stripe:PaymentIntentMismatchError |
400 | stripe:PaymentIntent identifier at B/P does not match the identifier provided by the C2 response for this Order . |
Every error returned from Stripe should map onto either stripe:ApiError
, stripe:CardError
, stripe:IdempotencyError
, or stripe:InvalidRequestError
depending on the type
returned in the Stripe error, which has a value of either api_error
, card_error
, idempotency_error
, or invalid_request_error
.
stripe:Error
is intended to be returned in the case of unexpected errors from Stripe (i.e. cases where Stripe itself does not return an error, such as a network failure) or errors relating to unexpected Stripe behaviour (i.e. where Stripe behaves in an inconsistent or unexpected way, such as a Payment Intent that was previously created at C2 not being found at B).
Note that stripe:IdempotencyError
and stripe:InvalidRequestError
should not occur if the Booking System has implemented this extension correctly.
Note also that stripe:MissingPaymentIntentDetailsError
, stripe:PaymentIntentMismatchError
and stripe:PaymentIntentContainsExcessivePropertiesError
relate to issues with Broker behaviour, and do not originate from Stripe.
Attributes from a Stripe error are mapped to properties in OpenBookingError
, with additional extension properties defined for use on the stripe:Error
.
Property | Stripe attribute | Type | Notes |
---|---|---|---|
@type |
- | schema:Text |
stripe:Error |
schema:name |
- | schema:Text |
A short, human-readable summary of the problem type, that MUST communicate the associated "Use Case" defined above. It SHOULD NOT change from occurrence to occurrence of the problem, except for purposes of localization. |
schema:description |
message |
schema:Text |
A human-readable message providing details about the specific occurrence of the problem. For stripe:CardError , these messages can be shown to end users. |
schema:url |
doc_url |
schema:URL |
A URL to more information about the error code reported. |
stripe:parameter |
param |
schema:Text |
If the error is parameter-specific, the parameter related to the error. For example, you can use this to display a message near the correct form field. |
stripe:errorCode |
code |
schema:Text |
For some errors that could be handled programmatically, a short string indicating the error code reported. |
stripe:declineCode |
decline_code |
schema:Text |
For card errors resulting from a card issuer decline, a short string indicating the decline code, which is the card issuer’s reason for the decline if they provide one. |
oa:instance |
request_log_url |
schema:URL |
A URL to the request log entry in the Stripe dashboard. |
oa:statusCode |
code |
schema:Integer |
An integer representing the HTTP status code, that MUST match the associated "Status Code" defined above. |
{
"@context": [
"https://openactive.io/",
"https://openactive.io/stripe-extension"
],
"@type": "stripe:CardError",
"name": "The user has provided a card that cannot be charged",
"description": "Your card's security code is incorrect. Please try again using the correct CVC.",
"stripe:parameter": "cvc",
"stripe:errorCode": "card_declined",
"stripe:declineCode": "incorrect_cvc",
"instance": "https://dashboard.stripe.com/test/logs/req_1234567890abcdef",
"statusCode": 400
}
Test interface actions are defined to simulate user interactions that would ordinarily be performed on the frontend via Stripe Elements.
This test action simulates the authorisation of a card by confirming the Payment Intent. This is useful for test code that uses Stripe Test Mode to test paid booking on the backend without requiring a frontend instance of Stripe Elements to authorise the card.
{
"@context": [
"https://openactive.io/",
"https://openactive.io/test-interface",
"https://openactive.io/stripe-extension"
],
"@type": "stripe:ConfirmPaymentIntentSimulateAction",
"object": {
"@type": "stripe:PaymentIntent",
"referencesOrder": {
"@type": "Order",
"seller": "https://example.com/api/organisations/123",
},
"identifier": "pi_1GPsnyKarmweGdVC5WhNworN" // payment intent ID
},
"stripe:paymentMethod": "pm_card_visa"
}
Note that the seller
MUST be included to allow the Booking System to deterimine which Stripe Account that the stripe:PaymentIntent
is associated with.
For a list of the various test values for stripe:paymentMethod
(e.g. pm_card_chargeDeclinedIncorrectCvc
) see the Stripe documentation.